This commit addresses failures in end-to-end tests that occurred due to
`ResizeObserver` loop limit exceptions.
These errors were triggered by Vue dependency upgrades in the commit
aae5434451.
The errors had the following message:
> `ResizeObserver loop completed with undelivered notifications`
This error happens when there are too many observations and the observer
is not able to deliver all observations within a single animation frame.
See: WICG/resize-observer#38
his commit resolves the issue by controlling how many observations are
delivered per animation frame and limiting it to only one.
It improves performance by reducing layout trashing, improving frame
rates, and managing resources more effectively.
Changes:
- Introduce an animation frame control to manage observations more
efficiently.
- Centralized `ResizeObserver` management within the `UseResizeObserver`
hook to improve consistency and reuse across the application.
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import { shallowRef } from 'vue';
|
|
import { useResizeObserver, type LifecycleHookRegistration, type ObservedElementReference } from '@/presentation/components/Shared/Hooks/Resize/UseResizeObserver';
|
|
import { flushPromiseResolutionQueue } from '@tests/unit/shared/PromiseInspection';
|
|
import type { AnimationFrameLimiter } from '@/presentation/components/Shared/Hooks/Resize/UseAnimationFrameLimiter';
|
|
import { ThrottleStub } from '@tests/unit/shared/Stubs/ThrottleStub';
|
|
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
|
|
|
describe('UseResizeObserver', () => {
|
|
it('registers observer once mounted', async () => {
|
|
// arrange
|
|
let registeredElement: Element | null = null;
|
|
const expectedElement = document.createElement('div');
|
|
const resizeObserverStub = createResizeObserverStub();
|
|
resizeObserverStub.observe = (element) => {
|
|
registeredElement = element;
|
|
};
|
|
// act
|
|
new TestContext()
|
|
.withObservedElementRef(shallowRef(expectedElement))
|
|
.withResizeObserver(resizeObserverStub)
|
|
.useResizeObserver();
|
|
await flushPromiseResolutionQueue();
|
|
// assert
|
|
expect(registeredElement).to.equal(expectedElement);
|
|
});
|
|
it('disposes observer once unmounted', async () => {
|
|
// arrange
|
|
let isObserverDisconnected = false;
|
|
const resizeObserverStub = createResizeObserverStub();
|
|
resizeObserverStub.disconnect = () => {
|
|
isObserverDisconnected = true;
|
|
};
|
|
let teardownCallback: (() => void) | undefined;
|
|
// act
|
|
new TestContext()
|
|
.withResizeObserver(resizeObserverStub)
|
|
.withOnTeardown((callback) => {
|
|
teardownCallback = callback;
|
|
})
|
|
.useResizeObserver();
|
|
await flushPromiseResolutionQueue();
|
|
expectExists(teardownCallback);
|
|
teardownCallback();
|
|
// assert
|
|
expect(isObserverDisconnected).to.equal(true);
|
|
});
|
|
});
|
|
|
|
function createResizeObserverStub(): ResizeObserver {
|
|
return {
|
|
disconnect: () => {},
|
|
observe: () => {},
|
|
unobserve: () => {},
|
|
};
|
|
}
|
|
|
|
function createFrameLimiterStub(): AnimationFrameLimiter {
|
|
return {
|
|
cancelNextFrame: () => {},
|
|
resetNextFrame: (callback) => { callback(); },
|
|
};
|
|
}
|
|
|
|
class TestContext {
|
|
private resizeObserver: ResizeObserver = createResizeObserverStub();
|
|
|
|
private observedElementRef: ObservedElementReference = shallowRef(document.createElement('div'));
|
|
|
|
private onSetup: LifecycleHookRegistration = (callback) => { callback(); };
|
|
|
|
private onTeardown: LifecycleHookRegistration = () => { };
|
|
|
|
public withResizeObserver(resizeObserver: ResizeObserver): this {
|
|
this.resizeObserver = resizeObserver;
|
|
return this;
|
|
}
|
|
|
|
public withObservedElementRef(observedElementRef: ObservedElementReference): this {
|
|
this.observedElementRef = observedElementRef;
|
|
return this;
|
|
}
|
|
|
|
public withOnSetup(onSetup: LifecycleHookRegistration): this {
|
|
this.onSetup = onSetup;
|
|
return this;
|
|
}
|
|
|
|
public withOnTeardown(onTeardown: LifecycleHookRegistration): this {
|
|
this.onTeardown = onTeardown;
|
|
return this;
|
|
}
|
|
|
|
public useResizeObserver() {
|
|
return useResizeObserver(
|
|
...this.buildParameters(),
|
|
);
|
|
}
|
|
|
|
private buildParameters(): Parameters<typeof useResizeObserver> {
|
|
return [
|
|
{
|
|
observedElementRef: this.observedElementRef,
|
|
throttleInMs: 50,
|
|
observeCallback: () => {},
|
|
},
|
|
() => ({
|
|
resizeObserverReady: Promise.resolve(() => this.resizeObserver),
|
|
}),
|
|
() => createFrameLimiterStub(),
|
|
new ThrottleStub()
|
|
.withImmediateExecution(true)
|
|
.func,
|
|
this.onSetup,
|
|
this.onTeardown,
|
|
];
|
|
}
|
|
}
|