Skip to content

Commit 5adbe7e

Browse files
authored
feat: add dev warning for missing field definition labels in attribute-editor (#4337)
1 parent 7d1f303 commit 5adbe7e

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
import { render } from '@testing-library/react';
5+
6+
import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
7+
8+
import AttributeEditor from '../../../lib/components/attribute-editor';
9+
10+
jest.mock('@cloudscape-design/component-toolkit', () => ({
11+
...jest.requireActual('@cloudscape-design/component-toolkit'),
12+
useContainerQuery: jest.fn().mockImplementation(() => ['m', () => {}]),
13+
}));
14+
15+
jest.mock('@cloudscape-design/component-toolkit/internal', () => ({
16+
...jest.requireActual('@cloudscape-design/component-toolkit/internal'),
17+
warnOnce: jest.fn(),
18+
}));
19+
20+
afterEach(() => {
21+
(warnOnce as jest.Mock).mockReset();
22+
});
23+
24+
describe('AttributeEditor component', () => {
25+
test('warns when a definition has no label', () => {
26+
render(
27+
<AttributeEditor
28+
addButtonText="Add"
29+
removeButtonText="Remove"
30+
definition={[{ label: 'Key label', control: () => 'key' }]}
31+
items={[{}]}
32+
gridLayout={[{ rows: [[1]] }]}
33+
/>
34+
);
35+
expect(warnOnce).not.toHaveBeenCalled();
36+
37+
render(
38+
<AttributeEditor
39+
addButtonText="Add"
40+
removeButtonText="Remove"
41+
definition={[{ label: 'Key label', control: () => 'key' }, { control: () => 'value' }]}
42+
items={[{}]}
43+
gridLayout={[{ rows: [[1, 1]] }]}
44+
/>
45+
);
46+
47+
expect(warnOnce).toHaveBeenCalledTimes(1);
48+
expect(warnOnce).toHaveBeenCalledWith(
49+
'AttributeEditor',
50+
'A `label` should be provided for each field definition. It is used as `aria-label` for accessibility.'
51+
);
52+
});
53+
});

src/attribute-editor/internal.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { useImperativeHandle, useRef, useState } from 'react';
44
import clsx from 'clsx';
55

6-
import { useMergeRefs, useUniqueId } from '@cloudscape-design/component-toolkit/internal';
6+
import { useMergeRefs, useUniqueId, warnOnce } from '@cloudscape-design/component-toolkit/internal';
77

88
import { ButtonProps } from '../button/interfaces';
99
import { InternalButton } from '../button/internal';
@@ -92,6 +92,16 @@ const InternalAttributeEditor = React.forwardRef(
9292
// eslint-disable-next-line react-hooks/exhaustive-deps
9393
}, [items, i18nStrings?.itemRemovedAriaLive]);
9494

95+
for (const def of definition) {
96+
if (def && !def.label) {
97+
warnOnce(
98+
'AttributeEditor',
99+
'A `label` should be provided for each field definition. It is used as `aria-label` for accessibility.'
100+
);
101+
break;
102+
}
103+
}
104+
95105
if (!gridLayout) {
96106
gridLayout = gridDefaults[definition.length];
97107
if (!gridLayout) {

0 commit comments

Comments
 (0)