Commit 0340a9f1 authored by Robert Knight's avatar Robert Knight

Fix flakey ProfileModal tests

The ProfileModal tests would sometimes fail with this error:

```
FAILED TESTS:
   "after each" hook for "shows modal on "openProfile" event"
    Chrome Headless 129.0.0.0 (Linux x86_64)
  Error: Failed to execute 'showModal' on 'HTMLDialogElement': The element is not in a Document.
```

The `HTMLDialogElement.showModal` call happens in an effect when the
`ModalDialog` component is rendered with the `isClosed` prop set to false. In
the ProfileModal tests, the component was rendered in a disconnected DOM node,
so this error should have happened on every run. However the
`emitter.publish("openProfile")` call which triggered this render was not
wrapped in `act` and so the effect which calls `showModal` was scheduled, but
often did not actually run before the component was unmounted in the `afterEach`
hook.

Fix the issue by:

 - Wrapping all `emitter.publish("openProfile")` calls in `act`, so they
   synchronously execute the effect.
 - Rendering the `ProfileModal` component in a connected DOM container
   which is removed after the test runs
 - For consistency, update the `NotebookModal` tests to work in the same
   way as the ProfileModal tests, with a single container element which
   is removed at the end of the test
parent 25b6147c
...@@ -8,6 +8,7 @@ import NotebookModal, { $imports } from '../NotebookModal'; ...@@ -8,6 +8,7 @@ import NotebookModal, { $imports } from '../NotebookModal';
describe('NotebookModal', () => { describe('NotebookModal', () => {
const notebookURL = 'https://test.hypothes.is/notebook'; const notebookURL = 'https://test.hypothes.is/notebook';
let container;
let components; let components;
let eventBus; let eventBus;
let emitter; let emitter;
...@@ -15,21 +16,20 @@ describe('NotebookModal', () => { ...@@ -15,21 +16,20 @@ describe('NotebookModal', () => {
const outerSelector = 'dialog[data-testid="notebook-outer"]'; const outerSelector = 'dialog[data-testid="notebook-outer"]';
const createComponent = config => { const createComponent = config => {
const attachTo = document.createElement('div');
document.body.appendChild(attachTo);
const component = mount( const component = mount(
<NotebookModal <NotebookModal
eventBus={eventBus} eventBus={eventBus}
config={{ notebookAppUrl: notebookURL, ...config }} config={{ notebookAppUrl: notebookURL, ...config }}
/>, />,
{ attachTo }, { attachTo: container },
); );
components.push([component, attachTo]); components.push(component);
return component; return component;
}; };
beforeEach(() => { beforeEach(() => {
container = document.createElement('div');
document.body.append(container);
components = []; components = [];
eventBus = new EventBus(); eventBus = new EventBus();
emitter = eventBus.createEmitter(); emitter = eventBus.createEmitter();
...@@ -46,10 +46,8 @@ describe('NotebookModal', () => { ...@@ -46,10 +46,8 @@ describe('NotebookModal', () => {
}); });
afterEach(() => { afterEach(() => {
components.forEach(([component, container]) => { components.forEach(component => component.unmount());
component.unmount(); container.remove();
container.remove();
});
$imports.$restore(); $imports.$restore();
}); });
...@@ -70,7 +68,9 @@ describe('NotebookModal', () => { ...@@ -70,7 +68,9 @@ describe('NotebookModal', () => {
assert.isFalse(outer.exists()); assert.isFalse(outer.exists());
assert.isFalse(wrapper.find('iframe').exists()); assert.isFalse(wrapper.find('iframe').exists());
emitter.publish('openNotebook', 'myGroup'); act(() => {
emitter.publish('openNotebook', 'myGroup');
});
wrapper.update(); wrapper.update();
outer = wrapper.find(outerSelector); outer = wrapper.find(outerSelector);
...@@ -86,7 +86,9 @@ describe('NotebookModal', () => { ...@@ -86,7 +86,9 @@ describe('NotebookModal', () => {
it('creates a new iframe element on every "openNotebook" event', () => { it('creates a new iframe element on every "openNotebook" event', () => {
const wrapper = createComponent(); const wrapper = createComponent();
emitter.publish('openNotebook', '1'); act(() => {
emitter.publish('openNotebook', '1');
});
wrapper.update(); wrapper.update();
const iframe1 = wrapper.find('iframe'); const iframe1 = wrapper.find('iframe');
...@@ -95,7 +97,9 @@ describe('NotebookModal', () => { ...@@ -95,7 +97,9 @@ describe('NotebookModal', () => {
addConfigFragment(notebookURL, { group: '1' }), addConfigFragment(notebookURL, { group: '1' }),
); );
emitter.publish('openNotebook', '1'); act(() => {
emitter.publish('openNotebook', '1');
});
wrapper.update(); wrapper.update();
const iframe2 = wrapper.find('iframe'); const iframe2 = wrapper.find('iframe');
...@@ -105,7 +109,9 @@ describe('NotebookModal', () => { ...@@ -105,7 +109,9 @@ describe('NotebookModal', () => {
); );
assert.notEqual(iframe1.getDOMNode(), iframe2.getDOMNode()); assert.notEqual(iframe1.getDOMNode(), iframe2.getDOMNode());
emitter.publish('openNotebook', '2'); act(() => {
emitter.publish('openNotebook', '2');
});
wrapper.update(); wrapper.update();
const iframe3 = wrapper.find('iframe'); const iframe3 = wrapper.find('iframe');
......
...@@ -7,6 +7,7 @@ import ProfileModal from '../ProfileModal'; ...@@ -7,6 +7,7 @@ import ProfileModal from '../ProfileModal';
describe('ProfileModal', () => { describe('ProfileModal', () => {
const profileURL = 'https://test.hypothes.is/user-profile'; const profileURL = 'https://test.hypothes.is/user-profile';
let container;
let components; let components;
let eventBus; let eventBus;
let emitter; let emitter;
...@@ -19,12 +20,15 @@ describe('ProfileModal', () => { ...@@ -19,12 +20,15 @@ describe('ProfileModal', () => {
eventBus={eventBus} eventBus={eventBus}
config={{ profileAppUrl: profileURL, ...config }} config={{ profileAppUrl: profileURL, ...config }}
/>, />,
{ attachTo: container },
); );
components.push(component); components.push(component);
return component; return component;
}; };
beforeEach(() => { beforeEach(() => {
container = document.createElement('div');
document.body.append(container);
components = []; components = [];
eventBus = new EventBus(); eventBus = new EventBus();
emitter = eventBus.createEmitter(); emitter = eventBus.createEmitter();
...@@ -32,6 +36,7 @@ describe('ProfileModal', () => { ...@@ -32,6 +36,7 @@ describe('ProfileModal', () => {
afterEach(() => { afterEach(() => {
components.forEach(component => component.unmount()); components.forEach(component => component.unmount());
container.remove();
}); });
it('does not render anything while the modal is closed', () => { it('does not render anything while the modal is closed', () => {
...@@ -42,7 +47,9 @@ describe('ProfileModal', () => { ...@@ -42,7 +47,9 @@ describe('ProfileModal', () => {
it('shows modal on "openProfile" event', () => { it('shows modal on "openProfile" event', () => {
const wrapper = createComponent(); const wrapper = createComponent();
emitter.publish('openProfile'); act(() => {
emitter.publish('openProfile');
});
wrapper.update(); wrapper.update();
assert.isTrue(wrapper.find(outerSelector).exists()); assert.isTrue(wrapper.find(outerSelector).exists());
...@@ -54,7 +61,9 @@ describe('ProfileModal', () => { ...@@ -54,7 +61,9 @@ describe('ProfileModal', () => {
it("removes the modal's content on closing", () => { it("removes the modal's content on closing", () => {
const wrapper = createComponent(); const wrapper = createComponent();
emitter.publish('openProfile'); act(() => {
emitter.publish('openProfile');
});
wrapper.update(); wrapper.update();
assert.isTrue(wrapper.find(outerSelector).exists()); assert.isTrue(wrapper.find(outerSelector).exists());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment