Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Avatar Converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Avatar Converged.badgeMask - RTL.normal.chromium.png 6 Changed
vr-tests-react-components/Avatar Converged.badgeMask.normal.chromium.png 4 Changed
vr-tests-react-components/CalendarCompat 5 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/CalendarCompat.multiDayView.default.chromium_1.png 2704 Changed
vr-tests-react-components/CalendarCompat.multiDayView.default.chromium.png 2434 Changed
vr-tests-react-components/CalendarCompat.multiDayView - Dark Mode.default.chromium.png 6713 Changed
vr-tests-react-components/CalendarCompat.multiDayView - High Contrast.default.chromium.png 7264 Changed
vr-tests-react-components/CalendarCompat.multiDayView - RTL.default.chromium.png 2323 Changed
vr-tests-react-components/Charts-DonutChart 6 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Basic - Dark Mode.default.chromium.png 8845 Changed
vr-tests-react-components/Charts-DonutChart.Basic - RTL.default.chromium.png 9673 Changed
vr-tests-react-components/Charts-DonutChart.Basic.default.chromium.png 9673 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic.default.chromium.png 10891 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic - RTL.default.chromium.png 10891 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 12504 Changed
vr-tests-react-components/DatePicker Compat 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/DatePicker Compat.default - Dark Mode.rest.chromium.png 371 Changed
vr-tests-react-components/DatePicker Compat.default - High Contrast.opened.chromium.png 443 Changed
vr-tests-react-components/DatePicker Compat.default.rest.chromium.png 311 Changed
vr-tests-react-components/Field 8 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Field.Checkbox.default.chromium.png 13 Changed
vr-tests-react-components/Field.ProgressBar.default.chromium.png 13 Changed
vr-tests-react-components/Field.RadioGroup.default.chromium.png 13 Changed
vr-tests-react-components/Field.Slider.default.chromium.png 13 Changed
vr-tests-react-components/Field.SpinButton.default.chromium.png 13 Changed
vr-tests-react-components/Field.Switch.default.chromium.png 13 Changed
vr-tests-react-components/Field.validation-error.default.chromium.png 13 Changed
vr-tests-react-components/Field.Textarea.default.chromium.png 13 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 416 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 619 Changed
vr-tests-react-components/MessageBar 8 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/MessageBar.Auto.default.chromium.png 17 Changed
vr-tests-react-components/MessageBar.Intents - Dark Mode.default.chromium.png 66 Changed
vr-tests-react-components/MessageBar.Intents - High Contrast.default.chromium.png 134 Changed
vr-tests-react-components/MessageBar.Square.default.chromium.png 17 Changed
vr-tests-react-components/MessageBar.Multiline Without Actions.default.chromium.png 98 Changed
vr-tests-react-components/MessageBar.Intents.default.chromium.png 98 Changed
vr-tests-react-components/MessageBar.Multiline No Actions.default.chromium.png 98 Changed
vr-tests-react-components/MessageBar.Multiline.default.chromium.png 98 Changed
vr-tests-react-components/Popover Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Popover Converged.when rendering inline, it should not render behind relatively positioned elements.PopoverSurface focused.chromium.png 93 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 497 Changed
vr-tests-react-components/Positioning.Positioning end.chromium.png 54 Changed
vr-tests-react-components/ProgressBar converged 4 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 208 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 404 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - RTL.default.chromium.png 162 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 326 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.disabled input hover.chromium.png 658 Changed
vr-tests-react-components/Toast 9 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Toast.Full Toast Inverted.Toast visible.chromium.png 8 Changed
vr-tests-react-components/Toast.Full Toast Inverted - Dark Mode.Toast visible.chromium.png 16 Changed
vr-tests-react-components/Toast.Full Toast Inverted - High Contrast.Toast visible.chromium.png 21 Changed
vr-tests-react-components/Toast.Title Only Inverted - High Contrast.Toast visible.chromium.png 21 Changed
vr-tests-react-components/Toast.Title Only Inverted.Toast visible.chromium.png 8 Changed
vr-tests-react-components/Toast.Title Only.Toast visible.chromium.png 16 Changed
vr-tests-react-components/Toast.Without Subtitle Inverted - High Contrast.Toast visible.chromium.png 21 Changed
vr-tests-react-components/Toast.Without Subtitle - RTL.Toast visible.chromium.png 16 Changed
vr-tests-react-components/Toast.Without Subtitle Inverted.Toast visible.chromium.png 8 Changed
vr-tests-react-components/Toolbar Converged 12 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Toolbar Converged.Far Group.Button Pressed.chromium.png 11 Changed
vr-tests-react-components/Toolbar Converged.Far Group.Toggle On.chromium.png 20 Changed
vr-tests-react-components/Toolbar Converged.Far Group.default.chromium.png 29 Changed
vr-tests-react-components/Toolbar Converged.Large.Button Pressed.chromium.png 5273 Changed
vr-tests-react-components/Toolbar Converged.Large.Toggle On.chromium.png 5209 Changed
vr-tests-react-components/Toolbar Converged.Large.default.chromium.png 5245 Changed
vr-tests-react-components/Toolbar Converged.Small.Button Pressed.chromium.png 109 Changed
vr-tests-react-components/Toolbar Converged.Small.Toggle On.chromium.png 92 Changed
vr-tests-react-components/Toolbar Converged.Small.default.chromium.png 110 Changed
vr-tests-react-components/Toolbar Converged.Vertical.Button Pressed.chromium.png 11 Changed
vr-tests-react-components/Toolbar Converged.Vertical.Toggle On.chromium.png 20 Changed
vr-tests-react-components/Toolbar Converged.Vertical.default.chromium.png 29 Changed

There were 23 duplicate changes discarded. Check the build logs for more information.

"type": "patch",
"comment": "fix: compare DrawerBody scroll positions with a tolerance so the footer divider is hidden when scrolled to the bottom at fractional browser zoom",
"packageName": "@fluentui/react-drawer",
"email": "fnambeh@gsumail.gram.edu",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { DrawerBody } from './DrawerBody';
import { DrawerProvider, useDrawerContextValue } from '../../contexts';
import { isConformant } from '../../testing/isConformant';

type ScrollMetrics = {
scrollTop: number;
clientHeight: number;
scrollHeight: number;
};

/**
* jsdom reports 0 for every layout measurement, so the values `getScrollState` reads are stubbed
* on the prototype. Fractional values reproduce what browsers report when zoom is not a whole
* number: `scrollTop` keeps its fraction while `clientHeight` and `scrollHeight` are rounded.
*/
const scrollMetrics: ScrollMetrics = { scrollTop: 0, clientHeight: 0, scrollHeight: 0 };

const ScrollStateHarness: React.FC = () => {
const contextValue = useDrawerContextValue();

return (
<DrawerProvider value={contextValue}>
<DrawerBody>Content</DrawerBody>
<div data-testid="scroll-state">{contextValue.scrollState}</div>
</DrawerProvider>
);
};

const renderWithScrollMetrics = (metrics: ScrollMetrics): string | null => {
Object.assign(scrollMetrics, metrics);

return render(<ScrollStateHarness />).getByTestId('scroll-state').textContent;
};

describe('DrawerBody', () => {
isConformant({
Component: DrawerBody,
Expand All @@ -21,4 +52,66 @@ describe('DrawerBody', () => {
</div>
`);
});

describe('scroll state', () => {
const originalDescriptors = new Map<keyof ScrollMetrics, PropertyDescriptor | undefined>();

beforeAll(() => {
(Object.keys(scrollMetrics) as (keyof ScrollMetrics)[]).forEach(key => {
originalDescriptors.set(key, Object.getOwnPropertyDescriptor(HTMLElement.prototype, key));
Object.defineProperty(HTMLElement.prototype, key, {
configurable: true,
get: () => scrollMetrics[key],
});
});
});

afterAll(() => {
originalDescriptors.forEach((descriptor, key) => {
if (descriptor) {
Object.defineProperty(HTMLElement.prototype, key, descriptor);
} else {
// These live on Element.prototype, so removing the stub restores the inherited accessor.
Reflect.deleteProperty(HTMLElement.prototype, key);
}
});
});

it('reports "none" when the content fits', () => {
expect(renderWithScrollMetrics({ scrollTop: 0, clientHeight: 300, scrollHeight: 300 })).toBe('none');
});

it('reports "top" when scrollable and not scrolled', () => {
expect(renderWithScrollMetrics({ scrollTop: 0, clientHeight: 300, scrollHeight: 500 })).toBe('top');
});

it('reports "middle" when scrolled between the ends', () => {
expect(renderWithScrollMetrics({ scrollTop: 100, clientHeight: 300, scrollHeight: 500 })).toBe('middle');
});

it('reports "bottom" when scrolled to the end', () => {
expect(renderWithScrollMetrics({ scrollTop: 200, clientHeight: 300, scrollHeight: 500 })).toBe('bottom');
});

// Regression: https://github.com/microsoft/fluentui/issues/36331
// At fractional browser zoom the scroll offset stops short of `scrollHeight - clientHeight`,
// which used to leave the state at 'middle' and kept the DrawerFooter divider visible.
// 198.5652 is the widest gap measured in Chromium, at 115% zoom.
const fractionalOffsets: Array<[string, number]> = [
['110% zoom', 199.0909],
['115% zoom', 198.5652],
['125% zoom', 199.8],
['150% zoom', 199.6667],
];

it.each(fractionalOffsets)('reports "bottom" when scrolled to the end at %s', (_zoom, scrollTop) => {
expect(renderWithScrollMetrics({ scrollTop, clientHeight: 300, scrollHeight: 500 })).toBe('bottom');
});

// Fractional layout can round `scrollHeight` a pixel above `clientHeight` on an element that
// cannot actually be scrolled, which used to report 'top' and show the divider.
it('reports "none" when overflow is within rounding error of the client height', () => {
expect(renderWithScrollMetrics({ scrollTop: 0, clientHeight: 300, scrollHeight: 301 })).toBe('none');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,38 @@ import type { DrawerScrollState } from '../../shared/DrawerBase.types';
import type { DrawerBodyProps, DrawerBodyState } from './DrawerBody.types';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';

/**
* `clientHeight` and `scrollHeight` are rounded to integers while `scrollTop` is fractional, so
* scroll positions have to be compared with a tolerance instead of for exact equality. Without it
* a fully scrolled body reports `middle` whenever the browser zoom is not a whole number.
*
* A non-scrollable element can report up to 1px of overflow that it cannot actually scroll, and
* both values are integers, so a single pixel is enough here.
*/
const OVERFLOW_TOLERANCE = 1;

/**
* A fully scrolled element can sit short of its own maximum scroll offset because `scrollTop` is
* fractional while the values it is compared against are rounded.
*/
const SCROLL_BOTTOM_TOLERANCE = 2;

/**
* Get the current scroll state of the DrawerBody.
*
* @internal
* @param element - HTMLElement to check scroll state of
*/
const getScrollState = ({ scrollTop, scrollHeight, clientHeight }: HTMLElement): DrawerScrollState => {
if (scrollHeight <= clientHeight) {
if (scrollHeight - clientHeight <= OVERFLOW_TOLERANCE) {
return 'none';
}

if (scrollTop === 0) {
return 'top';
}

if (scrollTop + clientHeight === scrollHeight) {
if (scrollHeight - clientHeight - scrollTop <= SCROLL_BOTTOM_TOLERANCE) {
return 'bottom';
}

Expand Down
Loading