Add interaction-tracking/subscriptions (#13426)

* Removed enableInteractionTrackingObserver as a separate flag; only enableInteractionTracking is used now

* Added interaction-tracking/subscriptions bundle and split tests

* Added multi-subscriber support

* Moved subscriptions behind feature flag

* Fixed bug with wrap() parameters and added test

* Replaced wrap arrow function
This commit is contained in:
Brian Vaughn
2018-08-17 14:45:18 -06:00
committed by GitHub
parent 4b32f525e1
commit 0da5102cf0
17 changed files with 857 additions and 654 deletions

View File

@@ -7,6 +7,7 @@
"LICENSE",
"README.md",
"index.js",
"subscriptions.js",
"cjs/",
"umd/"
],

View File

@@ -7,10 +7,7 @@
* @flow
*/
import {
enableInteractionTracking,
enableInteractionTrackingObserver,
} from 'shared/ReactFeatureFlags';
import {enableInteractionTracking} from 'shared/ReactFeatureFlags';
export type Interaction = {|
__count: number,
@@ -71,18 +68,15 @@ let threadIDCounter: number = 0;
let interactionsRef: InteractionsRef = (null: any);
// Listener(s) to notify when interactions begin and end.
// Note that subscribers are only supported when enableInteractionTrackingObserver is enabled.
let subscriberRef: SubscriberRef = (null: any);
if (enableInteractionTracking) {
interactionsRef = {
current: new Set(),
};
if (enableInteractionTrackingObserver) {
subscriberRef = {
current: null,
};
}
subscriberRef = {
current: null,
};
}
// These values are exported for libraries with advanced use cases (i.e. React).
@@ -127,7 +121,7 @@ export function track(
}
const interaction: Interaction = {
__count: 0,
__count: 1,
id: interactionIDCounter++,
name,
timestamp,
@@ -142,53 +136,42 @@ export function track(
interactions.add(interaction);
interactionsRef.current = interactions;
if (enableInteractionTrackingObserver) {
// Update before calling callback in case it schedules follow-up work.
interaction.__count = 1;
let returnValue;
const subscriber = subscriberRef.current;
const subscriber = subscriberRef.current;
let returnValue;
try {
if (subscriber !== null) {
subscriber.onInteractionTracked(interaction);
}
} finally {
try {
if (subscriber !== null) {
subscriber.onInteractionTracked(interaction);
subscriber.onWorkStarted(interactions, threadID);
}
} finally {
try {
if (subscriber !== null) {
subscriber.onWorkStarted(interactions, threadID);
}
returnValue = callback();
} finally {
interactionsRef.current = prevInteractions;
try {
returnValue = callback();
if (subscriber !== null) {
subscriber.onWorkStopped(interactions, threadID);
}
} finally {
interactionsRef.current = prevInteractions;
interaction.__count--;
try {
if (subscriber !== null) {
subscriber.onWorkStopped(interactions, threadID);
}
} finally {
interaction.__count--;
// If no async work was scheduled for this interaction,
// Notify subscribers that it's completed.
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
// If no async work was scheduled for this interaction,
// Notify subscribers that it's completed.
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
}
}
}
return returnValue;
} else {
try {
return callback();
} finally {
interactionsRef.current = prevInteractions;
}
}
return returnValue;
}
export function wrap(
@@ -201,89 +184,77 @@ export function wrap(
const wrappedInteractions = interactionsRef.current;
if (enableInteractionTrackingObserver) {
const subscriber = subscriberRef.current;
if (subscriber !== null) {
subscriber.onWorkScheduled(wrappedInteractions, threadID);
}
// Update the pending async work count for the current interactions.
// Update after calling subscribers in case of error.
wrappedInteractions.forEach(interaction => {
interaction.__count++;
});
let subscriber = subscriberRef.current;
if (subscriber !== null) {
subscriber.onWorkScheduled(wrappedInteractions, threadID);
}
const wrapped = () => {
// Update the pending async work count for the current interactions.
// Update after calling subscribers in case of error.
wrappedInteractions.forEach(interaction => {
interaction.__count++;
});
function wrapped() {
const prevInteractions = interactionsRef.current;
interactionsRef.current = wrappedInteractions;
if (enableInteractionTrackingObserver) {
const subscriber = subscriberRef.current;
subscriber = subscriberRef.current;
try {
let returnValue;
try {
if (subscriber !== null) {
subscriber.onWorkStarted(wrappedInteractions, threadID);
}
} finally {
try {
returnValue = callback.apply(undefined, arguments);
} finally {
interactionsRef.current = prevInteractions;
if (subscriber !== null) {
subscriber.onWorkStopped(wrappedInteractions, threadID);
}
}
}
return returnValue;
} finally {
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
});
}
} else {
try {
return callback.apply(undefined, arguments);
} finally {
interactionsRef.current = prevInteractions;
}
}
};
if (enableInteractionTrackingObserver) {
wrapped.cancel = () => {
const subscriber = subscriberRef.current;
try {
let returnValue;
try {
if (subscriber !== null) {
subscriber.onWorkCanceled(wrappedInteractions, threadID);
subscriber.onWorkStarted(wrappedInteractions, threadID);
}
} finally {
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
try {
returnValue = callback.apply(undefined, arguments);
} finally {
interactionsRef.current = prevInteractions;
if (subscriber && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
if (subscriber !== null) {
subscriber.onWorkStopped(wrappedInteractions, threadID);
}
});
}
}
};
return returnValue;
} finally {
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber !== null && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
});
}
}
wrapped.cancel = function cancel() {
subscriber = subscriberRef.current;
try {
if (subscriber !== null) {
subscriber.onWorkCanceled(wrappedInteractions, threadID);
}
} finally {
// Update pending async counts for all wrapped interactions.
// If this was the last scheduled async work for any of them,
// Mark them as completed.
wrappedInteractions.forEach(interaction => {
interaction.__count--;
if (subscriber && interaction.__count === 0) {
subscriber.onInteractionScheduledWorkCompleted(interaction);
}
});
}
};
return wrapped;
}

View File

@@ -0,0 +1,167 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Interaction, Subscriber} from './InteractionTracking';
import {enableInteractionTracking} from 'shared/ReactFeatureFlags';
import {__subscriberRef} from 'interaction-tracking';
let subscribers: Set<Subscriber> = (null: any);
if (enableInteractionTracking) {
subscribers = new Set();
}
export function subscribe(subscriber: Subscriber): void {
if (enableInteractionTracking) {
subscribers.add(subscriber);
}
}
export function unsubscribe(subscriber: Subscriber): void {
if (enableInteractionTracking) {
subscribers.delete(subscriber);
}
}
function onInteractionTracked(interaction: Interaction): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onInteractionTracked(interaction);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onInteractionScheduledWorkCompleted(interaction: Interaction): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onInteractionScheduledWorkCompleted(interaction);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkScheduled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkScheduled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkStarted(interactions: Set<Interaction>, threadID: number): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkStopped(interactions: Set<Interaction>, threadID: number): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkStopped(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
function onWorkCanceled(
interactions: Set<Interaction>,
threadID: number,
): void {
let didCatchError = false;
let caughtError = null;
subscribers.forEach(subscriber => {
try {
subscriber.onWorkCanceled(interactions, threadID);
} catch (error) {
if (!didCatchError) {
didCatchError = true;
caughtError = error;
}
}
});
if (didCatchError) {
throw caughtError;
}
}
if (enableInteractionTracking) {
__subscriberRef.current = {
onInteractionScheduledWorkCompleted,
onInteractionTracked,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
};
}

View File

@@ -15,10 +15,7 @@ describe('InteractionTracking', () => {
let advanceTimeBy;
let currentTime;
function loadModules({
enableInteractionTracking,
enableInteractionTrackingObserver,
}) {
function loadModules({enableInteractionTracking}) {
jest.resetModules();
jest.useFakeTimers();
@@ -31,7 +28,6 @@ describe('InteractionTracking', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableInteractionTracking = enableInteractionTracking;
ReactFeatureFlags.enableInteractionTrackingObserver = enableInteractionTrackingObserver;
InteractionTracking = require('interaction-tracking');
}
@@ -57,6 +53,18 @@ describe('InteractionTracking', () => {
expect(wrapped()).toBe(123);
});
it('should pass arguments through to a wrapped function', done => {
let wrapped;
InteractionTracking.track('arbitrary', currentTime, () => {
wrapped = InteractionTracking.wrap((param1, param2) => {
expect(param1).toBe('foo');
expect(param2).toBe('bar');
done();
});
});
wrapped('foo', 'bar');
});
it('should return an empty set when outside of a tracked event', () => {
expect(InteractionTracking.getCurrent()).toContainNoInteractions();
});
@@ -277,6 +285,12 @@ describe('InteractionTracking', () => {
});
describe('advanced integration', () => {
it('should return a unique threadID per request', () => {
expect(InteractionTracking.getThreadID()).not.toBe(
InteractionTracking.getThreadID(),
);
});
it('should expose the current set of interactions to be externally manipulated', () => {
InteractionTracking.track('outer event', currentTime, () => {
expect(InteractionTracking.__interactionsRef.current).toBe(
@@ -292,535 +306,12 @@ describe('InteractionTracking', () => {
]);
});
});
});
describe('interaction subscribers enabled', () => {
let onInteractionScheduledWorkCompleted;
let onInteractionTracked;
let onWorkCanceled;
let onWorkScheduled;
let onWorkStarted;
let onWorkStopped;
let subscriber;
let throwInOnInteractionScheduledWorkCompleted;
let throwInOnInteractionTracked;
let throwInOnWorkCanceled;
let throwInOnWorkScheduled;
let throwInOnWorkStarted;
let throwInOnWorkStopped;
const firstEvent = {id: 0, name: 'first', timestamp: 0};
const secondEvent = {id: 1, name: 'second', timestamp: 0};
const threadID = 123;
beforeEach(() => {
throwInOnInteractionScheduledWorkCompleted = false;
throwInOnInteractionTracked = false;
throwInOnWorkCanceled = false;
throwInOnWorkScheduled = false;
throwInOnWorkStarted = false;
throwInOnWorkStopped = false;
onInteractionScheduledWorkCompleted = jest.fn(() => {
if (throwInOnInteractionScheduledWorkCompleted) {
throw Error('Expected error onInteractionScheduledWorkCompleted');
}
});
onInteractionTracked = jest.fn(() => {
if (throwInOnInteractionTracked) {
throw Error('Expected error onInteractionTracked');
}
});
onWorkCanceled = jest.fn(() => {
if (throwInOnWorkCanceled) {
throw Error('Expected error onWorkCanceled');
}
});
onWorkScheduled = jest.fn(() => {
if (throwInOnWorkScheduled) {
throw Error('Expected error onWorkScheduled');
}
});
onWorkStarted = jest.fn(() => {
if (throwInOnWorkStarted) {
throw Error('Expected error onWorkStarted');
}
});
onWorkStopped = jest.fn(() => {
if (throwInOnWorkStopped) {
throw Error('Expected error onWorkStopped');
}
});
});
describe('enableInteractionTrackingObserver enabled', () => {
beforeEach(() => {
loadModules({
enableInteractionTracking: true,
enableInteractionTrackingObserver: true,
it('should expose a subscriber ref to be externally manipulated', () => {
InteractionTracking.track('outer event', currentTime, () => {
expect(InteractionTracking.__subscriberRef).toEqual({
current: null,
});
subscriber = {
onInteractionScheduledWorkCompleted,
onInteractionTracked,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
};
InteractionTracking.__subscriberRef.current = subscriber;
});
it('should return the value of a tracked function', () => {
expect(
InteractionTracking.track('arbitrary', currentTime, () => 123),
).toBe(123);
});
it('should return the value of a wrapped function', () => {
let wrapped;
InteractionTracking.track('arbitrary', currentTime, () => {
wrapped = InteractionTracking.wrap(() => 123);
});
expect(wrapped()).toBe(123);
});
describe('error handling', () => {
it('should cover onInteractionTracked/onWorkStarted within', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const mock = jest.fn();
// It should call the callback before re-throwing
throwInOnInteractionTracked = true;
expect(() =>
InteractionTracking.track(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onInteractionTracked');
throwInOnInteractionTracked = false;
expect(mock).toHaveBeenCalledTimes(1);
throwInOnWorkStarted = true;
expect(() =>
InteractionTracking.track(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onWorkStarted');
expect(mock).toHaveBeenCalledTimes(2);
// It should restore the previous/outer interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
firstEvent,
]);
done();
});
});
it('should cover onWorkStopped within track', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
let innerInteraction;
const mock = jest.fn(() => {
innerInteraction = Array.from(
InteractionTracking.getCurrent(),
)[1];
});
throwInOnWorkStopped = true;
expect(() =>
InteractionTracking.track(secondEvent.name, currentTime, mock),
).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous/outer interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(innerInteraction.__count).toBe(0);
done();
});
});
it('should cover the callback within track', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
expect(() => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
throw Error('Expected error callback');
});
}).toThrow('Expected error callback');
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
done();
});
it('should cover onWorkScheduled within wrap', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const interaction = Array.from(
InteractionTracking.getCurrent(),
)[0];
const beforeCount = interaction.__count;
throwInOnWorkScheduled = true;
expect(() => InteractionTracking.wrap(() => {})).toThrow(
'Expected error onWorkScheduled',
);
// It should not update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(beforeCount);
done();
});
});
it('should cover onWorkStarted within wrap', () => {
const mock = jest.fn();
let interaction, wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(mock);
});
expect(interaction.__count).toBe(1);
throwInOnWorkStarted = true;
expect(wrapped).toThrow('Expected error onWorkStarted');
// It should call the callback before re-throwing
expect(mock).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
});
it('should cover onWorkStopped within wrap', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const outerInteraction = Array.from(
InteractionTracking.getCurrent(),
)[0];
expect(outerInteraction.__count).toBe(1);
let wrapped;
let innerInteraction;
InteractionTracking.track(secondEvent.name, currentTime, () => {
innerInteraction = Array.from(
InteractionTracking.getCurrent(),
)[1];
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(1);
wrapped = InteractionTracking.wrap(jest.fn());
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(2);
});
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(1);
throwInOnWorkStopped = true;
expect(wrapped).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
outerInteraction,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(0);
done();
});
});
it('should cover the callback within wrap', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
let wrapped;
let interaction;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(() => {
throw Error('Expected error wrap');
});
});
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(wrapped).toThrow('Expected error wrap');
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork([interaction]);
done();
});
it('should cover onWorkCanceled within wrap', () => {
let interaction, wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(jest.fn());
});
expect(interaction.__count).toBe(1);
throwInOnWorkCanceled = true;
expect(wrapped.cancel).toThrow('Expected error onWorkCanceled');
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
});
});
it('calls lifecycle methods for track', () => {
expect(onInteractionTracked).not.toHaveBeenCalled();
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
InteractionTracking.track(
firstEvent.name,
currentTime,
() => {
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
expect(
onInteractionScheduledWorkCompleted,
).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
InteractionTracking.track(
secondEvent.name,
currentTime,
() => {
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(
onInteractionTracked,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(
onInteractionScheduledWorkCompleted,
).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(
1,
);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkScheduled).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
});
it('calls lifecycle methods for wrap', () => {
const unwrapped = jest.fn();
let wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
InteractionTracking.track(secondEvent.name, currentTime, () => {
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
secondEvent,
);
wrapped = InteractionTracking.wrap(unwrapped, threadID);
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkScheduled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
});
});
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrapped();
expect(unwrapped).toHaveBeenCalled();
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(3);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).toHaveBeenCalledTimes(3);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(
onInteractionScheduledWorkCompleted.mock.calls[0][0],
).toMatchInteraction(firstEvent);
expect(
onInteractionScheduledWorkCompleted.mock.calls[1][0],
).toMatchInteraction(secondEvent);
});
it('should call the correct interaction subscriber methods when a wrapped callback is canceled', () => {
const fnOne = jest.fn();
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
InteractionTracking.track(firstEvent.name, currentTime, () => {
wrappedOne = InteractionTracking.wrap(fnOne, threadID);
InteractionTracking.track(secondEvent.name, currentTime, () => {
wrappedTwo = InteractionTracking.wrap(fnTwo, threadID);
});
});
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
wrappedTwo.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
wrappedOne.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(2);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(fnOne).not.toHaveBeenCalled();
expect(fnTwo).not.toHaveBeenCalled();
});
it('should not end an interaction twice if wrap is used to schedule follow up work within another wrap', () => {
const fnOne = jest.fn(() => {
wrappedTwo = InteractionTracking.wrap(fnTwo, threadID);
});
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
InteractionTracking.track(firstEvent.name, currentTime, () => {
wrappedOne = InteractionTracking.wrap(fnOne, threadID);
});
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedOne();
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedTwo();
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
});
it('should unsubscribe', () => {
InteractionTracking.__subscriberRef.current = null;
InteractionTracking.track(firstEvent.name, currentTime, () => {});
expect(onInteractionTracked).not.toHaveBeenCalled();
});
describe('advanced integration', () => {
it('should return a unique threadID per request', () => {
expect(InteractionTracking.getThreadID()).not.toBe(
InteractionTracking.getThreadID(),
);
});
it('should expose the current set of interaction subscribers to be called externally', () => {
expect(
InteractionTracking.__subscriberRef.current.onInteractionTracked,
).toBe(onInteractionTracked);
});
});
});
describe('enableInteractionTrackingObserver disabled', () => {
beforeEach(() => {
loadModules({
enableInteractionTracking: true,
enableInteractionTrackingObserver: false,
});
});
it('should not create unnecessary objects', () => {
expect(InteractionTracking.__subscriberRef).toBe(null);
});
});
});

View File

@@ -0,0 +1,562 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
describe('InteractionTracking', () => {
let InteractionTracking;
let InteractionTrackingSubscriptions;
let ReactFeatureFlags;
let currentTime;
let onInteractionScheduledWorkCompleted;
let onInteractionTracked;
let onWorkCanceled;
let onWorkScheduled;
let onWorkStarted;
let onWorkStopped;
let throwInOnInteractionScheduledWorkCompleted;
let throwInOnInteractionTracked;
let throwInOnWorkCanceled;
let throwInOnWorkScheduled;
let throwInOnWorkStarted;
let throwInOnWorkStopped;
let firstSubscriber;
let secondSubscriber;
const firstEvent = {id: 0, name: 'first', timestamp: 0};
const secondEvent = {id: 1, name: 'second', timestamp: 0};
const threadID = 123;
function loadModules({enableInteractionTracking}) {
jest.resetModules();
jest.useFakeTimers();
currentTime = 0;
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableInteractionTracking = enableInteractionTracking;
InteractionTracking = require('interaction-tracking');
InteractionTrackingSubscriptions = require('interaction-tracking/subscriptions');
throwInOnInteractionScheduledWorkCompleted = false;
throwInOnInteractionTracked = false;
throwInOnWorkCanceled = false;
throwInOnWorkScheduled = false;
throwInOnWorkStarted = false;
throwInOnWorkStopped = false;
onInteractionScheduledWorkCompleted = jest.fn(() => {
if (throwInOnInteractionScheduledWorkCompleted) {
throw Error('Expected error onInteractionScheduledWorkCompleted');
}
});
onInteractionTracked = jest.fn(() => {
if (throwInOnInteractionTracked) {
throw Error('Expected error onInteractionTracked');
}
});
onWorkCanceled = jest.fn(() => {
if (throwInOnWorkCanceled) {
throw Error('Expected error onWorkCanceled');
}
});
onWorkScheduled = jest.fn(() => {
if (throwInOnWorkScheduled) {
throw Error('Expected error onWorkScheduled');
}
});
onWorkStarted = jest.fn(() => {
if (throwInOnWorkStarted) {
throw Error('Expected error onWorkStarted');
}
});
onWorkStopped = jest.fn(() => {
if (throwInOnWorkStopped) {
throw Error('Expected error onWorkStopped');
}
});
firstSubscriber = {
onInteractionScheduledWorkCompleted,
onInteractionTracked,
onWorkCanceled,
onWorkScheduled,
onWorkStarted,
onWorkStopped,
};
secondSubscriber = {
onInteractionScheduledWorkCompleted: jest.fn(),
onInteractionTracked: jest.fn(),
onWorkCanceled: jest.fn(),
onWorkScheduled: jest.fn(),
onWorkStarted: jest.fn(),
onWorkStopped: jest.fn(),
};
InteractionTrackingSubscriptions.subscribe(firstSubscriber);
InteractionTrackingSubscriptions.subscribe(secondSubscriber);
}
describe('enabled', () => {
beforeEach(() => loadModules({enableInteractionTracking: true}));
describe('error handling', () => {
it('should cover onInteractionTracked/onWorkStarted within', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const mock = jest.fn();
// It should call the callback before re-throwing
throwInOnInteractionTracked = true;
expect(() =>
InteractionTracking.track(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onInteractionTracked');
throwInOnInteractionTracked = false;
expect(mock).toHaveBeenCalledTimes(1);
throwInOnWorkStarted = true;
expect(() =>
InteractionTracking.track(
secondEvent.name,
currentTime,
mock,
threadID,
),
).toThrow('Expected error onWorkStarted');
expect(mock).toHaveBeenCalledTimes(2);
// It should restore the previous/outer interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onInteractionTracked).toHaveBeenCalledTimes(
3,
);
expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(3);
done();
});
});
it('should cover onWorkStopped within track', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
let innerInteraction;
const mock = jest.fn(() => {
innerInteraction = Array.from(InteractionTracking.getCurrent())[1];
});
throwInOnWorkStopped = true;
expect(() =>
InteractionTracking.track(secondEvent.name, currentTime, mock),
).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous/outer interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(innerInteraction.__count).toBe(0);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover onInteractionScheduledWorkCompleted within track', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const mock = jest.fn();
throwInOnInteractionScheduledWorkCompleted = true;
expect(() =>
InteractionTracking.track(secondEvent.name, currentTime, mock),
).toThrow('Expected error onInteractionScheduledWorkCompleted');
throwInOnInteractionScheduledWorkCompleted = false;
// It should restore the previous/outer interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
firstEvent,
]);
// It should call other subscribers despite the earlier error
expect(
secondSubscriber.onInteractionScheduledWorkCompleted,
).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover the callback within track', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
expect(() => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
throw Error('Expected error callback');
});
}).toThrow('Expected error callback');
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
done();
});
it('should cover onWorkScheduled within wrap', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const interaction = Array.from(InteractionTracking.getCurrent())[0];
const beforeCount = interaction.__count;
throwInOnWorkScheduled = true;
expect(() => InteractionTracking.wrap(() => {})).toThrow(
'Expected error onWorkScheduled',
);
// It should not update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(beforeCount);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkScheduled).toHaveBeenCalledTimes(1);
done();
});
});
it('should cover onWorkStarted within wrap', () => {
const mock = jest.fn();
let interaction, wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(mock);
});
expect(interaction.__count).toBe(1);
throwInOnWorkStarted = true;
expect(wrapped).toThrow('Expected error onWorkStarted');
// It should call the callback before re-throwing
expect(mock).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkStarted).toHaveBeenCalledTimes(2);
});
it('should cover onWorkStopped within wrap', done => {
InteractionTracking.track(firstEvent.name, currentTime, () => {
const outerInteraction = Array.from(
InteractionTracking.getCurrent(),
)[0];
expect(outerInteraction.__count).toBe(1);
let wrapped;
let innerInteraction;
InteractionTracking.track(secondEvent.name, currentTime, () => {
innerInteraction = Array.from(InteractionTracking.getCurrent())[1];
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(1);
wrapped = InteractionTracking.wrap(jest.fn());
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(2);
});
expect(outerInteraction.__count).toBe(2);
expect(innerInteraction.__count).toBe(1);
throwInOnWorkStopped = true;
expect(wrapped).toThrow('Expected error onWorkStopped');
throwInOnWorkStopped = false;
// It should restore the previous interactions
expect(InteractionTracking.getCurrent()).toMatchInteractions([
outerInteraction,
]);
// It should update the interaction count so as not to interfere with subsequent calls
expect(outerInteraction.__count).toBe(1);
expect(innerInteraction.__count).toBe(0);
expect(secondSubscriber.onWorkStopped).toHaveBeenCalledTimes(2);
done();
});
});
it('should cover the callback within wrap', done => {
expect(onWorkStarted).not.toHaveBeenCalled();
expect(onWorkStopped).not.toHaveBeenCalled();
let wrapped;
let interaction;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(() => {
throw Error('Expected error wrap');
});
});
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(wrapped).toThrow('Expected error wrap');
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork([interaction]);
done();
});
it('should cover onWorkCanceled within wrap', () => {
let interaction, wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
interaction = Array.from(InteractionTracking.getCurrent())[0];
wrapped = InteractionTracking.wrap(jest.fn());
});
expect(interaction.__count).toBe(1);
throwInOnWorkCanceled = true;
expect(wrapped.cancel).toThrow('Expected error onWorkCanceled');
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
// It should update the interaction count so as not to interfere with subsequent calls
expect(interaction.__count).toBe(0);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
// It should call other subscribers despite the earlier error
expect(secondSubscriber.onWorkCanceled).toHaveBeenCalledTimes(1);
});
});
it('calls lifecycle methods for track', () => {
expect(onInteractionTracked).not.toHaveBeenCalled();
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
InteractionTracking.track(
firstEvent.name,
currentTime,
() => {
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(1);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
InteractionTracking.track(
secondEvent.name,
currentTime,
() => {
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
secondEvent,
);
expect(
onInteractionScheduledWorkCompleted,
).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).not.toHaveBeenCalled();
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkStopped).toHaveBeenCalledTimes(1);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
},
threadID,
);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkScheduled).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
});
it('calls lifecycle methods for wrap', () => {
const unwrapped = jest.fn();
let wrapped;
InteractionTracking.track(firstEvent.name, currentTime, () => {
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
firstEvent,
);
InteractionTracking.track(secondEvent.name, currentTime, () => {
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionTracked).toHaveBeenLastNotifiedOfInteraction(
secondEvent,
);
wrapped = InteractionTracking.wrap(unwrapped, threadID);
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkScheduled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
});
});
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrapped();
expect(unwrapped).toHaveBeenCalled();
expect(onWorkScheduled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(3);
expect(onWorkStarted).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(onWorkStopped).toHaveBeenCalledTimes(3);
expect(onWorkStopped).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
expect(
onInteractionScheduledWorkCompleted.mock.calls[0][0],
).toMatchInteraction(firstEvent);
expect(
onInteractionScheduledWorkCompleted.mock.calls[1][0],
).toMatchInteraction(secondEvent);
});
it('should call the correct interaction subscriber methods when a wrapped callback is canceled', () => {
const fnOne = jest.fn();
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
InteractionTracking.track(firstEvent.name, currentTime, () => {
wrappedOne = InteractionTracking.wrap(fnOne, threadID);
InteractionTracking.track(secondEvent.name, currentTime, () => {
wrappedTwo = InteractionTracking.wrap(fnTwo, threadID);
});
});
expect(onInteractionTracked).toHaveBeenCalledTimes(2);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onWorkCanceled).not.toHaveBeenCalled();
expect(onWorkStarted).toHaveBeenCalledTimes(2);
expect(onWorkStopped).toHaveBeenCalledTimes(2);
wrappedTwo.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(secondEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(1);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent, secondEvent]),
threadID,
);
wrappedOne.cancel();
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(2);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
expect(onWorkCanceled).toHaveBeenCalledTimes(2);
expect(onWorkCanceled).toHaveBeenLastNotifiedOfWork(
new Set([firstEvent]),
threadID,
);
expect(fnOne).not.toHaveBeenCalled();
expect(fnTwo).not.toHaveBeenCalled();
});
it('should not end an interaction twice if wrap is used to schedule follow up work within another wrap', () => {
const fnOne = jest.fn(() => {
wrappedTwo = InteractionTracking.wrap(fnTwo, threadID);
});
const fnTwo = jest.fn();
let wrappedOne, wrappedTwo;
InteractionTracking.track(firstEvent.name, currentTime, () => {
wrappedOne = InteractionTracking.wrap(fnOne, threadID);
});
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedOne();
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
wrappedTwo();
expect(onInteractionTracked).toHaveBeenCalledTimes(1);
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
expect(
onInteractionScheduledWorkCompleted,
).toHaveBeenLastNotifiedOfInteraction(firstEvent);
});
it('should unsubscribe', () => {
InteractionTrackingSubscriptions.unsubscribe(firstSubscriber);
InteractionTracking.track(firstEvent.name, currentTime, () => {});
expect(onInteractionTracked).not.toHaveBeenCalled();
});
});
describe('disabled', () => {
beforeEach(() => loadModules({enableInteractionTracking: false}));
// TODO
});
});

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
export * from './src/InteractionTrackingSubscriptions';

View File

@@ -42,9 +42,6 @@ export const enableProfilerTimer = __PROFILE__;
// Track which interactions trigger each commit.
export const enableInteractionTracking = false;
// Track which interactions trigger each commit.
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {
invariant(false, 'Not implemented.');

View File

@@ -22,7 +22,6 @@ export const warnAboutLegacyContextAPI = __DEV__;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -22,7 +22,6 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -27,7 +27,6 @@ export const enableUserTimingAPI = __DEV__;
export const warnAboutLegacyContextAPI = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -22,7 +22,6 @@ export const warnAboutDeprecatedLifecycles = false;
export const warnAboutLegacyContextAPI = false;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -22,7 +22,6 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -22,7 +22,6 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -22,7 +22,6 @@ export const warnAboutLegacyContextAPI = false;
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
export const enableProfilerTimer = false;
export const enableInteractionTracking = false;
export const enableInteractionTrackingObserver = false;
// Only used in www builds.
export function addUserTimingListener() {

View File

@@ -33,7 +33,6 @@ export let enableUserTimingAPI = __DEV__;
export const enableProfilerTimer = __PROFILE__;
export const enableInteractionTracking = __PROFILE__;
export const enableInteractionTrackingObserver = __PROFILE__;
let refCount = 0;
export function addUserTimingListener() {

View File

@@ -404,6 +404,15 @@ const bundles = [
global: 'InteractionTracking',
externals: [],
},
{
label: 'interaction-tracking-subscriptions',
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
moduleType: ISOMORPHIC,
entry: 'interaction-tracking/subscriptions',
global: 'InteractionTrackingSubscriptions',
externals: ['interaction-tracking'],
},
];
// Based on deep-freeze by substack (public domain)

View File

@@ -21,6 +21,7 @@ const knownGlobals = Object.freeze({
react: 'React',
'react-dom': 'ReactDOM',
'interaction-tracking': 'InteractionTracking',
'interaction-tracking/subscriptions': 'InteractionTrackingSubscriptions',
});
// Given ['react'] in bundle externals, returns { 'react': 'React' }.