Skip to content

Commit 89568bb

Browse files
committed
chore: Detect and report missing css styles
1 parent 1ac1315 commit 89568bb

File tree

12 files changed

+174
-7
lines changed

12 files changed

+174
-7
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ src/index.ts
99
src/test-utils/dom/index.ts
1010
src/test-utils/selectors
1111
src/icon/generated
12-
src/internal/generated/custom-css-properties/index.*
12+
src/internal/generated/custom-css-properties
1313
vendor/generated-*.txt
1414
# IDEs
1515
.vscode
1616
# System
17-
.DS_Store
17+
.DS_Store

build-tools/tasks/clean.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = task('clean', () => {
1414
`${workspace.targetPath}/**`,
1515
`${workspace.staticSitePath}/**`,
1616
`${workspace.generatedTestUtils}/**`,
17+
`${workspace.generatedPath}/custom-css-properties/**`,
1718
`node_modules/.cache`,
1819
],
1920
{ glob: true }

build-tools/tasks/generate-custom-css-properties.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
const customCssPropertiesList = require('../../src/internal/generated/custom-css-properties/list');
43
const path = require('path');
5-
const { writeFile } = require('../utils/files');
64
const { getHashDigest } = require('loader-utils');
5+
const customCssPropertiesList = require('../utils/custom-css-properties');
6+
const { writeFile } = require('../utils/files');
7+
const workspace = require('../utils/workspace');
78

8-
const outputBasePath = path.join(__dirname, '../../src/internal/generated/custom-css-properties');
9+
const outputBasePath = path.join(workspace.generatedPath, 'custom-css-properties');
910
const hash = getHashDigest(Buffer.from(JSON.stringify(customCssPropertiesList)), 'md5', 'base36', 6);
1011

1112
const getHashedProperty = property => {
@@ -32,6 +33,9 @@ function writeSassFile() {
3233
writeFile(
3334
filepath,
3435
`
36+
// Build environment
37+
$awsui-commit-hash: ${workspace.gitCommitVersion};
38+
// Manually managed CSS-variables
3539
${customCssPropertiesList.map(property => `$${property}: ${getHashedProperty(property)};`).join('\n')}
3640
`
3741
);

build-tools/tasks/generate-environment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function writeEnvironmentFile(theme) {
1010
const values = {
1111
PACKAGE_SOURCE: workspace.packageSource,
1212
PACKAGE_VERSION: workspace.packageVersion,
13+
GIT_SHA: workspace.gitCommitVersion,
1314
THEME: theme.name,
1415
ALWAYS_VISUAL_REFRESH: !!theme.alwaysVisualRefresh,
1516
};
File renamed without changes.

build-tools/utils/workspace.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
isProd: process.env.NODE_ENV === 'production',
1212
packageSource,
1313
packageVersion,
14+
gitCommitVersion,
1415
sourcePath: 'src',
1516
generatedPath: 'src/internal/generated',
1617
generatedTestUtils: 'src/test-utils/selectors',

src/internal/base-component/styles.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@
99
*/
1010
@use 'awsui:globals';
1111
@use '../styles/global.scss';
12+
@use '../generated/custom-css-properties' as custom-styles;
13+
14+
:root {
15+
--awsui-version-info-#{custom-styles.$awsui-commit-hash}: true;
16+
}

src/internal/environment.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
export const THEME: string;
66
export const PACKAGE_SOURCE: string;
77
export const PACKAGE_VERSION: string;
8+
export const GIT_SHA: string;
89
/** Indicates that the current theme is always in visual refresh mode. */
910
export const ALWAYS_VISUAL_REFRESH: boolean;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { GIT_SHA } from '../../../../../lib/components/internal/environment';
5+
import { checkMissingStyles } from '../../../../../lib/components/internal/hooks/use-base-component/styles-check';
6+
import { metrics } from '../../../../../lib/components/internal/metrics';
7+
import { idleWithDelay } from '../styles-check';
8+
9+
jest.mock('../../../../../lib/components/internal/environment', () => ({
10+
...jest.requireActual('../../../../../lib/components/internal/environment'),
11+
PACKAGE_VERSION: '3.0.0 (abc)',
12+
GIT_SHA: 'abc',
13+
}));
14+
15+
afterEach(() => {
16+
jest.resetAllMocks();
17+
});
18+
19+
describe('checkMissingStyles', () => {
20+
let consoleWarnSpy: jest.SpyInstance;
21+
let sendPanoramaMetricSpy: jest.SpyInstance;
22+
const style = document.createElement('style');
23+
document.body.append(style);
24+
25+
beforeEach(() => {
26+
style.textContent = ``;
27+
consoleWarnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
28+
sendPanoramaMetricSpy = jest.spyOn(metrics, 'sendOpsMetricObject').mockImplementation(() => {});
29+
});
30+
31+
test('should pass the check if styles found', () => {
32+
// using :root does not work in JSDOM: https://github.com/jsdom/jsdom/issues/3563
33+
style.textContent = `
34+
body {
35+
--awsui-version-info-${GIT_SHA}: true;
36+
}
37+
`;
38+
checkMissingStyles();
39+
expect(consoleWarnSpy).not.toHaveBeenCalled();
40+
expect(sendPanoramaMetricSpy).not.toHaveBeenCalled();
41+
});
42+
43+
test('should detect missing styles', () => {
44+
checkMissingStyles();
45+
expect(consoleWarnSpy).toHaveBeenCalledWith(
46+
'Missing Cloudscape CSS for theme "default", version "3.0.0 (abc)", and git sha "abc".'
47+
);
48+
expect(sendPanoramaMetricSpy).toHaveBeenCalledWith('awsui-missing-css-asset', {});
49+
});
50+
51+
test('should report missing styles if a different version found', () => {
52+
style.textContent = `
53+
body {
54+
--awsui-version-info-c4d5e6: true;
55+
}
56+
`;
57+
checkMissingStyles();
58+
expect(consoleWarnSpy).toHaveBeenCalledWith(
59+
'Missing Cloudscape CSS for theme "default", version "3.0.0 (abc)", and git sha "abc".'
60+
);
61+
expect(sendPanoramaMetricSpy).toHaveBeenCalledWith('awsui-missing-css-asset', {});
62+
});
63+
});
64+
65+
describe('idleWithDelay', () => {
66+
const delay = (time: number) => new Promise(resolve => setTimeout(resolve, time));
67+
68+
beforeEach(() => {
69+
// simulate requestIdleCallback for JSDOM
70+
globalThis.requestIdleCallback = cb => setTimeout(cb, 0);
71+
});
72+
73+
test('does nothing if requestIdleCallback not supported', async () => {
74+
// @ts-expect-error simulate missing API
75+
globalThis.requestIdleCallback = undefined;
76+
const cb = jest.fn();
77+
expect(requestIdleCallback).toBe(undefined);
78+
idleWithDelay(cb);
79+
await delay(1100);
80+
expect(cb).not.toHaveBeenCalled();
81+
});
82+
83+
test('runs callback after a delay', async () => {
84+
const cb = jest.fn();
85+
idleWithDelay(cb);
86+
await delay(1100);
87+
expect(cb).toHaveBeenCalled();
88+
});
89+
90+
test('delay can be aborted', async () => {
91+
const cb = jest.fn();
92+
const abort = idleWithDelay(cb);
93+
await delay(500);
94+
abort!();
95+
await delay(600);
96+
expect(cb).not.toHaveBeenCalled();
97+
});
98+
});

src/internal/hooks/use-base-component/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AnalyticsMetadata } from '../../analytics/interfaces';
1313
import { PACKAGE_SOURCE, PACKAGE_VERSION, THEME } from '../../environment';
1414
import { getVisualTheme } from '../../utils/get-visual-theme';
1515
import { useVisualRefresh } from '../use-visual-mode';
16+
import { useMissingStylesCheck } from './styles-check';
1617

1718
export interface InternalBaseComponentProps<T = any> {
1819
__internalRootRef?: MutableRefObject<T | null> | null;
@@ -32,6 +33,7 @@ export default function useBaseComponent<T = any>(
3233
const theme = getVisualTheme(THEME, isVisualRefresh);
3334
useComponentMetrics(componentName, { packageSource: PACKAGE_SOURCE, packageVersion: PACKAGE_VERSION, theme }, config);
3435
useFocusVisible();
36+
useMissingStylesCheck();
3537
const elementRef = useComponentMetadata<T>(
3638
componentName,
3739
{ packageName: PACKAGE_SOURCE, version: PACKAGE_VERSION, theme },

0 commit comments

Comments
 (0)