diff --git a/src/components/AppNotification/AppNotification.tsx b/src/components/AppNotification/AppNotification.tsx
index cfbc51693..b8ef688d5 100644
--- a/src/components/AppNotification/AppNotification.tsx
+++ b/src/components/AppNotification/AppNotification.tsx
@@ -84,9 +84,7 @@ export const AppNotification = ({
onClick={onDismiss}
rank="tertiary"
variant={color === 'dark' ? 'inverse' : 'neutral'}
- >
- Close
-
+ >
)}
diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx
index 8516dd340..bee889a9f 100644
--- a/src/components/Button/Button.test.tsx
+++ b/src/components/Button/Button.test.tsx
@@ -7,6 +7,18 @@ import * as stories from './Button.stories';
import type { StoryFile } from '../../util/utility-types';
describe('', () => {
+ beforeEach(() => {
+ // Add in mocks for the calls that can occur in implementation to suppress logging in tests
+ const consoleMock = jest.spyOn(console, 'error');
+ const consoleWarnMock = jest.spyOn(console, 'warn');
+ consoleMock.mockImplementation();
+ consoleWarnMock.mockImplementation();
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
generateSnapshots(stories as StoryFile);
it('renders the text in the button', () => {
@@ -47,4 +59,32 @@ describe('', () => {
const button = screen.getByRole('button');
expect(button).toHaveFocus();
});
+
+ describe('emits messages when misused', () => {
+ let consoleErrorMock: jest.SpyInstance, consoleWarnMock: jest.SpyInstance;
+ beforeEach(() => {
+ consoleWarnMock = jest.spyOn(console, 'warn');
+ consoleErrorMock = jest.spyOn(console, 'error');
+ consoleWarnMock.mockImplementation();
+ consoleErrorMock.mockImplementation();
+ });
+
+ it('errors engineers when disable is used', () => {
+ render();
+
+ expect(consoleWarnMock).toHaveBeenCalledTimes(0);
+ expect(consoleErrorMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('warns when icon-only Button instances contain children', () => {
+ render(
+ ,
+ );
+
+ expect(consoleWarnMock).toHaveBeenCalledTimes(1);
+ expect(consoleErrorMock).toHaveBeenCalledTimes(0);
+ });
+ });
});
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index 255c29d2f..645840905 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -1,5 +1,6 @@
import clsx from 'clsx';
import React, { forwardRef } from 'react';
+import { assertEdsUsage } from '../../util/logging';
import type { Size } from '../../util/variant-types';
import Icon, { type IconName } from '../Icon';
import LoadingIndicator from '../LoadingIndicator';
@@ -132,6 +133,20 @@ export const Button = forwardRef(
isLoading && styles['button--is-loading'],
);
+ assertEdsUsage(
+ [
+ typeof isDisabled === 'undefined' &&
+ typeof other.disabled !== 'undefined',
+ ],
+ 'Use "isDisabled" instead of "disabled" on button instances',
+ 'error',
+ );
+
+ assertEdsUsage(
+ [iconLayout === 'icon-only' && typeof children !== 'undefined'],
+ 'Specifying content for "children" when using icon-only layout is not required and can be removed.',
+ );
+
return (
', () => {
+ beforeEach(() => {
+ const consoleMock = jest.spyOn(console, 'warn');
+ consoleMock.mockImplementation();
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
generateSnapshots(stories as StoryFile);
it('renders the text in the link', () => {
@@ -63,4 +72,42 @@ describe('', () => {
const link = screen.getByRole('link');
expect(link).toHaveFocus();
});
+
+ describe('emits warnings when misused', () => {
+ it('warns when inline links are using emphasis=low', () => {
+ const consoleMock = jest.spyOn(console, 'warn');
+ consoleMock.mockImplementation();
+ render(
+
+ Click
+ ,
+ );
+
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('warns when inline links have icons specified', () => {
+ const consoleMock = jest.spyOn(console, 'warn');
+ consoleMock.mockImplementation();
+ render(
+
+ Click
+ ,
+ );
+
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ });
+
+ it('warns when chevron-right is not used in low emphasis mode', () => {
+ const consoleMock = jest.spyOn(console, 'warn');
+ consoleMock.mockImplementation();
+ render(
+
+ Click
+ ,
+ );
+
+ expect(consoleMock).toHaveBeenCalledTimes(1);
+ });
+ });
});
diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx
index 55fbba83b..e261e1f25 100644
--- a/src/components/Link/Link.tsx
+++ b/src/components/Link/Link.tsx
@@ -1,5 +1,6 @@
import clsx from 'clsx';
import React, { forwardRef } from 'react';
+import { assertEdsUsage } from '../../util/logging';
import type { Size } from '../../util/variant-types';
import Icon, { type IconName } from '../Icon';
@@ -83,6 +84,21 @@ export const Link = forwardRef(
const iconSize = size && (['xl', 'lg'].includes(size) ? '1.5rem' : '1rem');
+ assertEdsUsage(
+ [context === 'inline' && emphasis === 'low'],
+ 'Inline links cannot be lowEmphasis',
+ );
+
+ assertEdsUsage(
+ [context === 'inline' && !!icon],
+ 'Inline links cannot show icons',
+ );
+
+ assertEdsUsage(
+ [icon === 'chevron-right' && emphasis !== 'low'],
+ 'Icon "chevron-right" only allowed when lowEmphasis is used',
+ );
+
return (
{children}
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
index c7a4ba4ff..87ee5fb67 100644
--- a/src/components/Modal/Modal.tsx
+++ b/src/components/Modal/Modal.tsx
@@ -253,9 +253,7 @@ export const ModalContent = (props: ModalContentProps) => {
onClick={onClose}
rank="tertiary"
variant="neutral"
- >
- Close
-
+ >
)}
{children}
diff --git a/src/components/PageNotification/PageNotification.tsx b/src/components/PageNotification/PageNotification.tsx
index 3d75aa364..a9090f09c 100644
--- a/src/components/PageNotification/PageNotification.tsx
+++ b/src/components/PageNotification/PageNotification.tsx
@@ -114,9 +114,7 @@ export const PageNotification = ({
rank="tertiary"
size="lg"
variant="neutral"
- >
- Close
-
+ >
)}
);
diff --git a/src/components/Slider/Slider.stories.tsx b/src/components/Slider/Slider.stories.tsx
index 472a4c868..c8c43b57a 100644
--- a/src/components/Slider/Slider.stories.tsx
+++ b/src/components/Slider/Slider.stories.tsx
@@ -198,7 +198,7 @@ export const UsingControlButtons: Story = {
)}
);
diff --git a/src/util/findLowestTenMultiplier.ts b/src/util/findLowestTenMultiplier.ts
index f9f08c388..8ed94e1ce 100644
--- a/src/util/findLowestTenMultiplier.ts
+++ b/src/util/findLowestTenMultiplier.ts
@@ -1,3 +1,5 @@
+import { assertEdsUsage } from './logging';
+
/**
* Returns the lowest multiple of 10 that multiplies with all numbers in a list to make them integers.
* Useful for floating point math.
@@ -8,12 +10,12 @@
* @returns {number} Lowest multiple of 10.
*/
export function findLowestTenMultiplier(numbers: number[]): number {
- if (
- process.env.NODE_ENV !== 'production' &&
- numbers.some((number) => !Number.isFinite(number))
- ) {
- throw 'Number should be a real finite number';
- }
+ assertEdsUsage(
+ [numbers.some((number) => !Number.isFinite(number))],
+ 'Number should be a real finite number',
+ 'error',
+ );
+
let multiplier = 1;
while (
numbers.some((number) => !Number.isInteger((number * multiplier) % 1))
diff --git a/src/util/logging.ts b/src/util/logging.ts
new file mode 100644
index 000000000..1f05aacfd
--- /dev/null
+++ b/src/util/logging.ts
@@ -0,0 +1,22 @@
+import identity from 'lodash/identity';
+
+type Check = boolean;
+type LogLevel = 'warn' | 'error';
+
+/**
+ * Logging function used to check whether a usage of EDS is proper and advised. When using, it defaults
+ * to warning, where it will print a message to the console for developers to see. LogLevel supported.
+ *
+ * @param checks set of boolean checks to assert whether the component usages are compatible
+ * @param message Message to print when the component is not being used as advised
+ * @param [loglevel] Severity of the tracked issue
+ */
+export function assertEdsUsage(
+ checks: Check[],
+ message: string,
+ loglevel: LogLevel = 'warn',
+): void {
+ if (process.env.NODE_ENV !== 'production' && [...checks].some(identity)) {
+ console[loglevel](message);
+ }
+}