Skip to content

Commit 99e999e

Browse files
refactor(atomic): migrate CopyButton functional component to Lit (#6598)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: alexprudhomme <[email protected]>
1 parent 8792769 commit 99e999e

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {html} from 'lit';
2+
import {describe, expect, it, vi} from 'vitest';
3+
import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture';
4+
import {type CopyButtonProps, renderCopyButton} from './copy-button';
5+
6+
describe('#renderCopyButton', () => {
7+
const locators = (element: Element) => ({
8+
get button() {
9+
return element.querySelector('[part="copy-button"]');
10+
},
11+
get icon() {
12+
return element.querySelector('atomic-icon');
13+
},
14+
get iconContainer() {
15+
return element.querySelector('.icon-container');
16+
},
17+
});
18+
19+
const renderComponent = async (props: Partial<CopyButtonProps> = {}) => {
20+
return await renderFunctionFixture(
21+
html`${renderCopyButton({
22+
props: {
23+
title: 'Copy',
24+
isCopied: false,
25+
error: false,
26+
onClick: vi.fn(),
27+
...props,
28+
},
29+
})}`
30+
);
31+
};
32+
33+
it('should render a copy button in the document', async () => {
34+
const element = await renderComponent();
35+
const button = locators(element).button;
36+
expect(button).toBeInTheDocument();
37+
});
38+
39+
it('should render a copy button with the correct title', async () => {
40+
const element = await renderComponent({
41+
title: 'Copy to clipboard',
42+
});
43+
const button = locators(element).button;
44+
45+
expect(button).toHaveAttribute('title', 'Copy to clipboard');
46+
});
47+
48+
it('should render a copy button with the correct part attribute', async () => {
49+
const element = await renderComponent();
50+
const button = locators(element).button;
51+
52+
expect(button).toHaveAttribute('part', 'copy-button');
53+
});
54+
55+
it('should render a copy button with default classes', async () => {
56+
const element = await renderComponent();
57+
const button = locators(element).button;
58+
59+
expect(button).toHaveClass('rounded-md');
60+
expect(button).toHaveClass('p-2');
61+
});
62+
63+
it('should add "copied" class when isCopied is true', async () => {
64+
const element = await renderComponent({
65+
isCopied: true,
66+
});
67+
const button = locators(element).button;
68+
69+
expect(button).toHaveClass('copied');
70+
expect(button).toHaveClass('rounded-md');
71+
expect(button).toHaveClass('p-2');
72+
});
73+
74+
it('should add "error" class when error is true', async () => {
75+
const element = await renderComponent({
76+
error: true,
77+
});
78+
const button = locators(element).button;
79+
80+
expect(button).toHaveClass('error');
81+
expect(button).toHaveClass('rounded-md');
82+
expect(button).toHaveClass('p-2');
83+
});
84+
85+
it('should add both "copied" and "error" classes when both are true', async () => {
86+
const element = await renderComponent({
87+
isCopied: true,
88+
error: true,
89+
});
90+
const button = locators(element).button;
91+
92+
expect(button).toHaveClass('copied');
93+
expect(button).toHaveClass('error');
94+
expect(button).toHaveClass('rounded-md');
95+
expect(button).toHaveClass('p-2');
96+
});
97+
98+
it('should call onClick handler when clicked', async () => {
99+
const handleClick = vi.fn();
100+
const element = await renderComponent({
101+
onClick: handleClick,
102+
});
103+
const button = locators(element).button as HTMLButtonElement;
104+
105+
button.click();
106+
107+
expect(handleClick).toHaveBeenCalled();
108+
});
109+
110+
it('should render an atomic-icon component', async () => {
111+
const element = await renderComponent();
112+
const icon = locators(element).icon;
113+
114+
expect(icon).toBeInTheDocument();
115+
});
116+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {html} from 'lit';
2+
import {renderButton} from '@/src/components/common/button';
3+
import type {FunctionalComponent} from '@/src/utils/functional-component-utils';
4+
import CopyIcon from '../../../images/copy.svg';
5+
import '@/src/components/common/atomic-icon/atomic-icon';
6+
7+
export interface CopyButtonProps {
8+
title: string;
9+
isCopied: boolean;
10+
error: boolean;
11+
onClick: () => void;
12+
}
13+
14+
export const renderCopyButton: FunctionalComponent<CopyButtonProps> = ({
15+
props,
16+
}) => {
17+
const classes = ['rounded-md', 'p-2'];
18+
if (props.isCopied) {
19+
classes.push('copied');
20+
}
21+
if (props.error) {
22+
classes.push('error');
23+
}
24+
25+
return renderButton({
26+
props: {
27+
title: props.title,
28+
part: 'copy-button',
29+
style: 'text-transparent',
30+
class: classes.join(' '),
31+
onClick: props.onClick,
32+
},
33+
})(html`
34+
<div class="icon-container text-neutral-dark">
35+
<atomic-icon class="w-5" .icon=${CopyIcon}></atomic-icon>
36+
</div>
37+
`);
38+
};

packages/atomic/src/components/common/generated-answer/generated-answer-common.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import {
1515
import {getNamedSlotContent} from '../../../utils/slot-utils';
1616
import {AnyBindings} from '../interface/bindings';
1717
import {Heading} from '../stencil-heading';
18+
import {CopyButton} from './stencil-copy-button';
1819
import {Switch} from '../stencil-switch';
19-
import {CopyButton} from './copy-button';
2020
import {FeedbackButton} from './stencil-feedback-button';
2121
import {GeneratedContentContainer} from './generated-content-container';
2222
import {ShowButton} from './stencil-show-button';

0 commit comments

Comments
 (0)