Skip to content

Commit

Permalink
Add support for newState and oldState
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed May 1, 2024
1 parent b394c00 commit 83bcaab
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 0 deletions.
8 changes: 8 additions & 0 deletions packages/react-dom-bindings/src/events/SyntheticEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,3 +592,11 @@ const WheelEventInterface = {
};
export const SyntheticWheelEvent: $FlowFixMe =
createSyntheticEvent(WheelEventInterface);

const ToggleEventInterface = {
...EventInterface,
newState: '',
oldState: '',
};
export const SyntheticToggleEvent: EventInterfaceType =
createSyntheticEvent(ToggleEventInterface);
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
SyntheticWheelEvent,
SyntheticClipboardEvent,
SyntheticPointerEvent,
SyntheticToggleEvent,
} from '../../events/SyntheticEvent';

import {
Expand Down Expand Up @@ -161,6 +162,11 @@ function extractEvents(
case 'pointerup':
SyntheticEventCtor = SyntheticPointerEvent;
break;
case 'toggle':
case 'beforetoggle':
// MDN claims <details> should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle
SyntheticEventCtor = SyntheticToggleEvent;
break;
default:
// Unknown event. This is used by createEventHandle.
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@

'use strict';

// polyfill missing JSDOM support
class ToggleEvent extends Event {
constructor(type, eventInit) {
super(type, eventInit);
this.newState = eventInit.newState;
this.oldState = eventInit.oldState;
}
}

describe('SimpleEventPlugin', function () {
let React;
let ReactDOMClient;
Expand Down Expand Up @@ -469,5 +478,116 @@ describe('SimpleEventPlugin', function () {
'wheel',
]);
});

it('dispatches synthetic toggle events when the Popover API is used', async () => {
container = document.createElement('div');

const onToggle = jest.fn();
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<>
<button popoverTargetElement="popover">Toggle popover</button>
<div id="popover" popover="" onToggle={onToggle}>
popover content
</div>
</>,
);
});

const target = container.querySelector('#popover');
target.dispatchEvent(
new ToggleEvent('toggle', {
bubbles: false,
cancelable: true,
oldState: 'closed',
newState: 'open',
}),
);

expect(onToggle).toHaveBeenCalledTimes(1);
let event = onToggle.mock.calls[0][0];
expect(event).toEqual(
expect.objectContaining({
oldState: 'closed',
newState: 'open',
}),
);

target.dispatchEvent(
new ToggleEvent('toggle', {
bubbles: false,
cancelable: true,
oldState: 'open',
newState: 'closed',
}),
);

expect(onToggle).toHaveBeenCalledTimes(2);
event = onToggle.mock.calls[1][0];
expect(event).toEqual(
expect.objectContaining({
oldState: 'open',
newState: 'closed',
}),
);
});

it('dispatches synthetic toggle events when <details> is used', async () => {
// This test just replays browser behavior.
// The real test would be if browsers dispatch ToggleEvent on <details>.
// This case only exists because MDN claims <details> doesn't receive ToggleEvent.
// However, Chrome dispatches ToggleEvent on <details> and the spec confirms that behavior: https://html.spec.whatwg.org/multipage/indices.html#event-toggle

container = document.createElement('div');

const onToggle = jest.fn();
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<details id="details" onToggle={onToggle}>
<summary>Summary</summary>
Details
</details>,
);
});

const target = container.querySelector('#details');
target.dispatchEvent(
new ToggleEvent('toggle', {
bubbles: false,
cancelable: true,
oldState: 'closed',
newState: 'open',
}),
);

expect(onToggle).toHaveBeenCalledTimes(1);
let event = onToggle.mock.calls[0][0];
expect(event).toEqual(
expect.objectContaining({
oldState: 'closed',
newState: 'open',
}),
);

target.dispatchEvent(
new ToggleEvent('toggle', {
bubbles: false,
cancelable: true,
oldState: 'open',
newState: 'closed',
}),
);

expect(onToggle).toHaveBeenCalledTimes(2);
event = onToggle.mock.calls[1][0];
expect(event).toEqual(
expect.objectContaining({
oldState: 'open',
newState: 'closed',
}),
);
});
});
});

0 comments on commit 83bcaab

Please sign in to comment.