Skip to content

Commit ba62005

Browse files
authored
feat: Optimize visual refresh detection and add development warning (#567)
1 parent 2cbc7c1 commit ba62005

File tree

3 files changed

+48
-30
lines changed

3 files changed

+48
-30
lines changed

src/internal/hooks/use-visual-mode/__tests__/use-visual-mode.test.tsx

+24-12
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
import React, { useRef } from 'react';
4-
import { useCurrentMode, useDensityMode, useVisualRefresh } from '../index';
4+
import { useCurrentMode, useDensityMode, useVisualRefresh, clearVisualRefreshState } from '../index';
55
import { render, screen } from '@testing-library/react';
66
import { mutate } from './utils';
77

8-
const originalFn = window.CSS.supports;
9-
beforeEach(() => {
10-
window.CSS.supports = jest.fn().mockReturnValue(true);
11-
});
12-
13-
afterEach(() => {
14-
window.CSS.supports = originalFn;
15-
});
16-
178
jest.mock('../../../environment', () => ({ ALWAYS_VISUAL_REFRESH: false }), { virtual: true });
189

1910
describe('useCurrentMode', () => {
@@ -83,11 +74,32 @@ describe('useVisualRefresh', () => {
8374
return <div data-testid="current-mode">{isRefresh.toString()}</div>;
8475
}
8576

86-
test('should return false when CSS-variables are not supported', () => {
87-
(window.CSS.supports as jest.Mock).mockReturnValue(false);
77+
beforeEach(() => clearVisualRefreshState());
78+
afterEach(() => document.body.classList.remove('awsui-visual-refresh'));
79+
afterEach(() => jest.restoreAllMocks());
80+
81+
test('should return false when class name is not present', () => {
8882
render(<App />);
8983
expect(screen.getByTestId('current-mode')).toHaveTextContent('false');
9084
});
85+
86+
test('should return true when class name is present', () => {
87+
document.body.classList.add('awsui-visual-refresh');
88+
render(<App />);
89+
expect(screen.getByTestId('current-mode')).toHaveTextContent('true');
90+
});
91+
92+
test('should print a warning when late visual refresh class name was detected', () => {
93+
jest.spyOn(console, 'warn').mockImplementation(() => {});
94+
const { rerender } = render(<App />);
95+
expect(screen.getByTestId('current-mode')).toHaveTextContent('false');
96+
expect(console.warn).not.toHaveBeenCalled();
97+
98+
document.body.classList.add('awsui-visual-refresh');
99+
rerender(<App />);
100+
expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/Dynamic visual refresh change detected/));
101+
expect(screen.getByTestId('current-mode')).toHaveTextContent('false');
102+
});
91103
});
92104

93105
// The above suites cover majority of cases

src/internal/hooks/use-visual-mode/__tests__/use-visual-refresh.test.tsx src/internal/hooks/use-visual-mode/__tests__/use-visual-refresh-static.test.tsx

-9
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,6 @@ import { render, screen } from '@testing-library/react';
66

77
jest.mock('../../../environment', () => ({ ALWAYS_VISUAL_REFRESH: true }), { virtual: true });
88

9-
const originalFn = window.CSS.supports;
10-
beforeEach(() => {
11-
window.CSS.supports = jest.fn().mockReturnValue(true);
12-
});
13-
14-
afterEach(() => {
15-
window.CSS.supports = originalFn;
16-
});
17-
189
describe('useVisualRefresh with locked visual refresh mode', () => {
1910
function App() {
2011
const isRefresh = useVisualRefresh();

src/internal/hooks/use-visual-mode/index.ts

+24-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ALWAYS_VISUAL_REFRESH } from '../../environment';
55
import { isMotionDisabled } from '../../motion';
66
import { findUpUntil } from '../../utils/dom';
77
import { useMutationObserver } from '../use-mutation-observer';
8+
import { isDevelopment } from '../../is-development';
9+
import { warnOnce } from '../../logging';
810

911
export function useCurrentMode(elementRef: React.RefObject<HTMLElement>) {
1012
const [value, setValue] = useState<'light' | 'dark'>('light');
@@ -30,19 +32,32 @@ export function useDensityMode(elementRef: React.RefObject<HTMLElement>) {
3032
return value;
3133
}
3234

35+
export const useVisualRefresh = ALWAYS_VISUAL_REFRESH ? () => true : useVisualRefreshDynamic;
36+
3337
// We expect VR is to be set only once and before the application is rendered.
3438
let visualRefreshState: undefined | boolean = undefined;
3539

36-
export function useVisualRefresh() {
40+
// for testing
41+
export function clearVisualRefreshState() {
42+
visualRefreshState = undefined;
43+
}
44+
45+
function detectVisualRefresh() {
46+
return typeof document !== 'undefined' && !!document.querySelector('.awsui-visual-refresh');
47+
}
48+
49+
export function useVisualRefreshDynamic() {
3750
if (visualRefreshState === undefined) {
38-
const supportsCSSVariables = typeof window !== 'undefined' && window.CSS?.supports?.('color', 'var(--test-var)');
39-
40-
if (ALWAYS_VISUAL_REFRESH) {
41-
visualRefreshState = true;
42-
} else if (!supportsCSSVariables) {
43-
visualRefreshState = false;
44-
} else {
45-
visualRefreshState = !!document.querySelector('.awsui-visual-refresh');
51+
visualRefreshState = detectVisualRefresh();
52+
}
53+
if (isDevelopment) {
54+
const newVisualRefreshState = detectVisualRefresh();
55+
if (newVisualRefreshState !== visualRefreshState) {
56+
warnOnce(
57+
'Visual Refresh',
58+
'Dynamic visual refresh change detected. This is not supported. ' +
59+
'Make sure `awsui-visual-refresh` is attached to the `<body>` element before initial React render'
60+
);
4661
}
4762
}
4863
return visualRefreshState;

0 commit comments

Comments
 (0)