Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions __mocks__/@juggle/resize-observer.ts

This file was deleted.

11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"prepare": "husky"
},
"dependencies": {
"@juggle/resize-observer": "^3.3.1",
"tslib": "^2.3.1"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import React from 'react';
import { render } from '@testing-library/react';
import { ContainerQueryEntry } from '../interfaces';
import useContainerQuery from '../use-container-query';

function TestComponent({ mapFn = () => '' }: { mapFn?: (entry: ContainerQueryEntry) => string }) {
const [value, ref] = useContainerQuery(mapFn);
return <div ref={ref} data-testid="test" data-value={value} />;
}

test('should work in JSDOM environment without any mocks', () => {
// making sure this API does not exist
expect(typeof ResizeObserver).toBe('undefined');
const mapFn = jest.fn(() => '');
const component = render(<TestComponent mapFn={mapFn} />);
expect(mapFn).toHaveBeenCalledWith(
{
target: component.getByTestId('test'),
contentBoxWidth: 0,
contentBoxHeight: 0,
borderBoxWidth: 0,
borderBoxHeight: 0,
},
null
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import useContainerQuery from '../use-container-query';
import { ResizeObserver } from '@juggle/resize-observer';
import { ContainerQueryEntry } from '../interfaces';
import '../../internal/container-queries/__tests__/resize-observer-mock';

function TestComponent({ mapFn = () => '' }: { mapFn?: (entry: ContainerQueryEntry) => string }) {
const [value, ref] = useContainerQuery(mapFn);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

/**
* Mock ResizeObserver for Jest tests
*
* This module provides a mock implementation of the native ResizeObserver API
* for use in Jest test environments where ResizeObserver may not be available.
*/

const mockObserve = jest.fn();
const mockUnobserve = jest.fn();
const mockDisconnect = jest.fn();

// Create the ResizeObserver mock constructor
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: mockObserve,
unobserve: mockUnobserve,
disconnect: mockDisconnect,
}));

// Mock the prototype methods for backward compatibility with tests that access prototype
Object.defineProperty(global.ResizeObserver, 'prototype', {
value: {
observe: mockObserve,
unobserve: mockUnobserve,
disconnect: mockDisconnect,
},
writable: false,
});

/**
* Mock getBoundingClientRect to return dimensions based on CSS styles
*
* In JSDOM test environments, getBoundingClientRect() returns all zeros because
* JSDOM doesn't perform actual layout calculations. This mock extracts width/height
* from computed CSS styles to provide realistic dimensions for ResizeObserver tests.
*
* Only mock in browser environments (not SSR tests where Element is undefined).
*/
if (typeof Element !== 'undefined') {
Element.prototype.getBoundingClientRect = jest.fn(function () {
const style = window.getComputedStyle(this);
const width = parseFloat(style.width) || 0;
const height = parseFloat(style.height) || 0;

return {
width,
height,
top: 0,
left: 0,
bottom: height,
right: width,
x: 0,
y: 0,
toJSON: () => ({}),
};
});
}

// Export the mock functions for test access if needed
module.exports = {
mockObserve,
mockUnobserve,
mockDisconnect,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import React, { useState, useRef } from 'react';
import { render } from '@testing-library/react';
import { useResizeObserver } from '../use-resize-observer';
import { ResizeObserver } from '@juggle/resize-observer';
import { ContainerQueryEntry } from '../interfaces';
import './resize-observer-mock';

function TestComponent({ mapFn = () => '' }: { mapFn?: (entry: ContainerQueryEntry) => string }) {
const ref = useRef<HTMLDivElement>(null);
Expand Down
22 changes: 19 additions & 3 deletions src/internal/container-queries/use-resize-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0

import { unstable_batchedUpdates } from 'react-dom';
import { ResizeObserver, ResizeObserverEntry } from '@juggle/resize-observer';
import { useEffect, useLayoutEffect } from 'react';
import { ContainerQueryEntry, ElementReference } from './interfaces';
import { useStableCallback } from '../stable-callback';
Expand Down Expand Up @@ -37,12 +36,13 @@

// This effect provides a synchronous update required to prevent flakiness when initial state and first observed state are different.
// Can potentially conflict with React concurrent mode: https://17.reactjs.org/docs/concurrent-mode-intro.html.
// TODO: A possible solution would be to make consumers not render any content until the first (asynchronous) observation is available.

Check warning on line 39 in src/internal/container-queries/use-resize-observer.ts

View workflow job for this annotation

GitHub Actions / build / build

Unexpected 'todo' comment: 'TODO: A possible solution would be to...'

Check warning on line 39 in src/internal/container-queries/use-resize-observer.ts

View workflow job for this annotation

GitHub Actions / dry-run / Build component-toolkit

Unexpected 'todo' comment: 'TODO: A possible solution would be to...'
useLayoutEffect(
() => {
const element = typeof elementRef === 'function' ? elementRef() : elementRef?.current;
if (element) {
onObserve(convertResizeObserverEntry(new ResizeObserverEntry(element)));
const rect = element.getBoundingClientRect();
onObserve(convertElementToEntry(element, rect));
}
},
// This effect is only needed for the first render to provide a synchronous update.
Expand All @@ -52,7 +52,7 @@

useEffect(() => {
const element = typeof elementRef === 'function' ? elementRef() : elementRef?.current;
if (element) {
if (element && typeof ResizeObserver !== 'undefined') {
let connected = true;
const observer = new ResizeObserver(entries => {
// Prevent observe notifications on already unmounted component.
Expand Down Expand Up @@ -80,3 +80,19 @@
borderBoxHeight: entry.borderBoxSize[0].blockSize,
};
}

function convertElementToEntry(element: Element, rect: DOMRect): ContainerQueryEntry {
const computedStyle = window.getComputedStyle(element);
const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;
const paddingRight = parseFloat(computedStyle.paddingRight) || 0;
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;

return {
target: element,
contentBoxWidth: rect.width - paddingLeft - paddingRight,
contentBoxHeight: rect.height - paddingTop - paddingBottom,
borderBoxWidth: rect.width,
borderBoxHeight: rect.height,
};
}
Loading