mirror of
https://github.com/status-im/react-navigation.git
synced 2025-02-24 17:18:09 +00:00
Fix and actually understand event emitter (#3367)
This commit is contained in:
parent
d0da40fa32
commit
c88ec60fe3
@ -204,7 +204,7 @@ test('grandchildren transitions', () => {
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(2);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
@ -213,67 +213,102 @@ test('grandchildren transitions', () => {
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(3);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('pass through focus', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
test('grandchildren pass through transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
);
|
||||
const childEventSubscriber = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
'testKey'
|
||||
'key1'
|
||||
);
|
||||
const testRoute = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'testKey' }],
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
};
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
].slice(0, childIndex + 1),
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(0, true);
|
||||
const blurred2State = makeFakeState(0, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'willFocus',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'willBlur',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'didBlur',
|
||||
state: testRoute,
|
||||
lastState: testRoute,
|
||||
action: { type: 'FooAction' },
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
test('child focus with transition', () => {
|
||||
|
@ -28,16 +28,21 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
}
|
||||
};
|
||||
|
||||
const emit = payload => {
|
||||
const subscribers = getChildSubscribers(payload.type);
|
||||
const emit = (type, payload) => {
|
||||
const payloadWithType = { ...payload, type };
|
||||
const subscribers = getChildSubscribers(type);
|
||||
subscribers &&
|
||||
subscribers.forEach(subs => {
|
||||
subs(payload);
|
||||
subs(payloadWithType);
|
||||
});
|
||||
};
|
||||
|
||||
let isParentFocused = true;
|
||||
let isChildFocused = false;
|
||||
// lastEmittedEvent keeps track of focus state for one route. First we assume
|
||||
// we are blurred. If we are focused on initialization, the first 'action'
|
||||
// event will cause onFocus+willFocus events because we had previously been
|
||||
// considered blurred
|
||||
let lastEmittedEvent = 'didBlur';
|
||||
|
||||
const cleanup = () => {
|
||||
upstreamSubscribers.forEach(subs => subs && subs.remove());
|
||||
};
|
||||
@ -55,110 +60,75 @@ export default function getChildEventSubscriber(addListener, key) {
|
||||
const { state, lastState, action } = payload;
|
||||
const lastRoutes = lastState && lastState.routes;
|
||||
const routes = state && state.routes;
|
||||
|
||||
const lastFocusKey =
|
||||
lastState && lastState.routes && lastState.routes[lastState.index].key;
|
||||
const focusKey = routes && routes[state.index].key;
|
||||
|
||||
const isFocused = focusKey === key;
|
||||
const wasFocused = lastFocusKey === key;
|
||||
const isChildFocused = focusKey === key;
|
||||
const lastRoute =
|
||||
lastRoutes && lastRoutes.find(route => route.key === key);
|
||||
const newRoute = routes && routes.find(route => route.key === key);
|
||||
const eventContext = payload.context || 'Root';
|
||||
const childPayload = {
|
||||
context: `${key}:${action.type}_${eventContext}`,
|
||||
context: `${key}:${action.type}_${payload.context || 'Root'}`,
|
||||
state: newRoute,
|
||||
lastState: lastRoute,
|
||||
action,
|
||||
type: eventName,
|
||||
};
|
||||
|
||||
const isTransitioning = !!state && state.isTransitioning;
|
||||
const wasTransitioning = !!lastState && lastState.isTransitioning;
|
||||
const didStartTransitioning = !wasTransitioning && isTransitioning;
|
||||
const didFinishTransitioning = wasTransitioning && !isTransitioning;
|
||||
const wasChildFocused = isChildFocused;
|
||||
if (eventName !== 'action') {
|
||||
switch (eventName) {
|
||||
case 'didFocus':
|
||||
isParentFocused = true;
|
||||
break;
|
||||
case 'didBlur':
|
||||
isParentFocused = false;
|
||||
break;
|
||||
|
||||
const previouslyLastEmittedEvent = lastEmittedEvent;
|
||||
|
||||
if (lastEmittedEvent === 'didBlur') {
|
||||
// The child is currently blurred. Look for willFocus conditions
|
||||
if (eventName === 'willFocus' && isChildFocused) {
|
||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
||||
} else if (eventName === 'action' && isChildFocused) {
|
||||
emit((lastEmittedEvent = 'willFocus'), childPayload);
|
||||
}
|
||||
if (isFocused && eventName === 'willFocus') {
|
||||
emit(childPayload);
|
||||
}
|
||||
if (isFocused && !isTransitioning && eventName === 'didFocus') {
|
||||
emit(childPayload);
|
||||
isChildFocused = true;
|
||||
if (lastEmittedEvent === 'willFocus') {
|
||||
// We are currently mid-focus. Look for didFocus conditions.
|
||||
// If state.isTransitioning is false, this child event happens immediately after willFocus
|
||||
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
|
||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
isChildFocused &&
|
||||
!isTransitioning
|
||||
) {
|
||||
emit((lastEmittedEvent = 'didFocus'), childPayload);
|
||||
}
|
||||
if (isFocused && eventName === 'willBlur') {
|
||||
emit(childPayload);
|
||||
}
|
||||
if (isFocused && !isTransitioning && eventName === 'didBlur') {
|
||||
emit(childPayload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// now we're exclusively handling the "action" event
|
||||
if (!isParentFocused) {
|
||||
return;
|
||||
if (lastEmittedEvent === 'didFocus') {
|
||||
// The child is currently focused. Look for blurring events
|
||||
if (!isChildFocused) {
|
||||
// The child is no longer focused within this navigation state
|
||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
||||
} else if (eventName === 'willBlur') {
|
||||
// The parent is getting a willBlur event
|
||||
emit((lastEmittedEvent = 'willBlur'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
previouslyLastEmittedEvent === 'didFocus'
|
||||
) {
|
||||
// While focused, pass action events to children for grandchildren focus
|
||||
emit('action', childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (isChildFocused && newRoute) {
|
||||
// fire this action event to pass navigation events to children subscribers
|
||||
emit(childPayload);
|
||||
if (lastEmittedEvent === 'willBlur') {
|
||||
// The child is mid-blur. Wait for transition to end
|
||||
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
|
||||
// The child is done blurring because transitioning is over, or isTransitioning
|
||||
// never began and didBlur fires immediately after willBlur
|
||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||
} else if (eventName === 'didBlur') {
|
||||
// Pass through the parent didBlur event if it happens
|
||||
emit((lastEmittedEvent = 'didBlur'), childPayload);
|
||||
}
|
||||
if (isFocused && didStartTransitioning && !isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willFocus',
|
||||
});
|
||||
}
|
||||
if (isFocused && didFinishTransitioning && !isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didFocus',
|
||||
});
|
||||
isChildFocused = true;
|
||||
}
|
||||
if (isFocused && !isChildFocused && !didStartTransitioning) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willFocus',
|
||||
});
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didFocus',
|
||||
});
|
||||
isChildFocused = true;
|
||||
}
|
||||
if (!isFocused && didStartTransitioning && isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willBlur',
|
||||
});
|
||||
}
|
||||
if (!isFocused && didFinishTransitioning && isChildFocused) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didBlur',
|
||||
});
|
||||
isChildFocused = false;
|
||||
}
|
||||
if (!isFocused && isChildFocused && !didStartTransitioning) {
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'willBlur',
|
||||
});
|
||||
emit({
|
||||
...childPayload,
|
||||
type: 'didBlur',
|
||||
});
|
||||
isChildFocused = false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user