Skip to content

Commit 00146ce

Browse files
committed
fixing Import
1 parent e0deabe commit 00146ce

File tree

6 files changed

+254
-58
lines changed

6 files changed

+254
-58
lines changed

client/modules/IDE/components/CopyableInput.jsx renamed to client/modules/IDE/components/CopyableInput/CopyableInput.jsx

File renamed without changes.

client/modules/IDE/components/CopyableInput.stories.jsx renamed to client/modules/IDE/components/CopyableInput/CopyableInput.stories.jsx

File renamed without changes.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent, act } from '@testing-library/react';
3+
import Clipboard from 'clipboard';
4+
import CopyableInput from './CopyableInput';
5+
6+
// Mock clipboard.js
7+
jest.mock('clipboard');
8+
9+
// Mock react-i18next
10+
jest.mock('react-i18next', () => ({
11+
useTranslation: () => ({
12+
t: (key) => key
13+
})
14+
}));
15+
16+
// Mock ShareIcon SVG — must be a function, not a plain object
17+
jest.mock('../../../images/share.svg', () => {
18+
const ShareIcon = () => <svg data-testid="share-icon" />;
19+
return ShareIcon;
20+
});
21+
22+
describe('CopyableInput', () => {
23+
const defaultProps = {
24+
label: 'Test Label',
25+
value: 'https://example.com'
26+
};
27+
28+
let mockClipboardInstance;
29+
30+
beforeEach(() => {
31+
mockClipboardInstance = {
32+
on: jest.fn(),
33+
destroy: jest.fn()
34+
};
35+
Clipboard.mockImplementation(() => mockClipboardInstance);
36+
});
37+
38+
afterEach(() => {
39+
jest.clearAllMocks();
40+
});
41+
42+
// ─── Rendering ────────────────────────────────────────────────────────────
43+
44+
describe('rendering', () => {
45+
it('renders without crashing', () => {
46+
render(<CopyableInput {...defaultProps} />);
47+
expect(screen.getByRole('textbox')).toBeInTheDocument();
48+
});
49+
50+
it('renders the label text', () => {
51+
render(<CopyableInput {...defaultProps} />);
52+
expect(screen.getByText('Test Label')).toBeInTheDocument();
53+
});
54+
55+
it('renders the input with the correct value', () => {
56+
render(<CopyableInput {...defaultProps} />);
57+
expect(screen.getByRole('textbox')).toHaveValue('https://example.com');
58+
});
59+
60+
it('renders the input as readOnly', () => {
61+
render(<CopyableInput {...defaultProps} />);
62+
expect(screen.getByRole('textbox')).toHaveAttribute('readonly');
63+
});
64+
65+
it('associates label with input via htmlFor', () => {
66+
render(<CopyableInput {...defaultProps} />);
67+
const input = screen.getByRole('textbox');
68+
const label = screen.getByText('Test Label').closest('label');
69+
expect(label).toHaveAttribute('for', input.id);
70+
});
71+
72+
it('does not render the preview link by default', () => {
73+
render(<CopyableInput {...defaultProps} />);
74+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
75+
});
76+
77+
it('renders the preview link when hasPreviewLink is true', () => {
78+
render(<CopyableInput {...defaultProps} hasPreviewLink />);
79+
const link = screen.getByRole('link');
80+
expect(link).toBeInTheDocument();
81+
expect(link).toHaveAttribute('href', defaultProps.value);
82+
});
83+
84+
it('opens the preview link in a new tab', () => {
85+
render(<CopyableInput {...defaultProps} hasPreviewLink />);
86+
const link = screen.getByRole('link');
87+
expect(link).toHaveAttribute('target', '_blank');
88+
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
89+
});
90+
});
91+
92+
// ─── CSS Classes ──────────────────────────────────────────────────────────
93+
94+
describe('CSS classes', () => {
95+
it('applies the base copyable-input class', () => {
96+
const { container } = render(<CopyableInput {...defaultProps} />);
97+
expect(container.firstChild).toHaveClass('copyable-input');
98+
});
99+
100+
it('does not apply the --with-preview modifier by default', () => {
101+
const { container } = render(<CopyableInput {...defaultProps} />);
102+
expect(container.firstChild).not.toHaveClass(
103+
'copyable-input--with-preview'
104+
);
105+
});
106+
107+
it('applies the --with-preview modifier when hasPreviewLink is true', () => {
108+
const { container } = render(
109+
<CopyableInput {...defaultProps} hasPreviewLink />
110+
);
111+
expect(container.firstChild).toHaveClass('copyable-input--with-preview');
112+
});
113+
114+
it('does not apply tooltip class initially', () => {
115+
render(<CopyableInput {...defaultProps} />);
116+
const valueContainer = screen
117+
.getByRole('textbox')
118+
.closest('.copyable-input__value-container');
119+
expect(valueContainer).not.toHaveClass('tooltipped');
120+
expect(valueContainer).not.toHaveClass('tooltipped-n');
121+
});
122+
});
123+
124+
// ─── Clipboard Integration ────────────────────────────────────────────────
125+
126+
describe('clipboard integration', () => {
127+
it('initialises Clipboard on mount', () => {
128+
render(<CopyableInput {...defaultProps} />);
129+
expect(Clipboard).toHaveBeenCalledTimes(1);
130+
});
131+
132+
it('passes the input element as the clipboard target', () => {
133+
render(<CopyableInput {...defaultProps} />);
134+
const [, options] = Clipboard.mock.calls[0];
135+
const input = screen.getByRole('textbox');
136+
expect(options.target()).toBe(input);
137+
});
138+
139+
it('registers a success handler on the clipboard instance', () => {
140+
render(<CopyableInput {...defaultProps} />);
141+
expect(mockClipboardInstance.on).toHaveBeenCalledWith(
142+
'success',
143+
expect.any(Function)
144+
);
145+
});
146+
147+
it('shows the tooltip after a successful copy', () => {
148+
render(<CopyableInput {...defaultProps} />);
149+
150+
const successCallback = mockClipboardInstance.on.mock.calls.find(
151+
([event]) => event === 'success'
152+
)[1];
153+
154+
act(() => {
155+
successCallback();
156+
});
157+
158+
const valueContainer = screen
159+
.getByRole('textbox')
160+
.closest('.copyable-input__value-container');
161+
expect(valueContainer).toHaveClass('tooltipped');
162+
expect(valueContainer).toHaveClass('tooltipped-n');
163+
});
164+
165+
it('destroys the clipboard instance on unmount', () => {
166+
const { unmount } = render(<CopyableInput {...defaultProps} />);
167+
unmount();
168+
expect(mockClipboardInstance.destroy).toHaveBeenCalledTimes(1);
169+
});
170+
});
171+
172+
// ─── Tooltip / Mouse Interactions ─────────────────────────────────────────
173+
174+
describe('tooltip behaviour', () => {
175+
it('hides the tooltip on mouse leave after a copy', () => {
176+
render(<CopyableInput {...defaultProps} />);
177+
178+
const successCallback = mockClipboardInstance.on.mock.calls.find(
179+
([event]) => event === 'success'
180+
)[1];
181+
182+
act(() => {
183+
successCallback();
184+
});
185+
186+
const valueContainer = screen
187+
.getByRole('textbox')
188+
.closest('.copyable-input__value-container');
189+
190+
expect(valueContainer).toHaveClass('tooltipped');
191+
192+
fireEvent.mouseLeave(valueContainer);
193+
194+
expect(valueContainer).not.toHaveClass('tooltipped');
195+
expect(valueContainer).not.toHaveClass('tooltipped-n');
196+
});
197+
198+
it('sets the aria-label on the value container', () => {
199+
render(<CopyableInput {...defaultProps} />);
200+
const valueContainer = screen
201+
.getByRole('textbox')
202+
.closest('.copyable-input__value-container');
203+
expect(valueContainer).toHaveAttribute(
204+
'aria-label',
205+
'CopyableInput.CopiedARIA'
206+
);
207+
});
208+
});
209+
210+
// ─── Accessibility ────────────────────────────────────────────────────────
211+
212+
describe('accessibility', () => {
213+
it('renders the preview link with an aria-label when hasPreviewLink is true', () => {
214+
render(<CopyableInput {...defaultProps} hasPreviewLink />);
215+
const link = screen.getByRole('link');
216+
expect(link).toHaveAttribute('aria-label', 'CopyableInput.CopiedARIA');
217+
});
218+
});
219+
220+
// ─── Props ────────────────────────────────────────────────────────────────
221+
222+
describe('prop handling', () => {
223+
it('updates the displayed value when the value prop changes', () => {
224+
const { rerender } = render(<CopyableInput {...defaultProps} />);
225+
rerender(<CopyableInput {...defaultProps} value="https://updated.com" />);
226+
expect(screen.getByRole('textbox')).toHaveValue('https://updated.com');
227+
});
228+
229+
it('reflects a new label in the DOM when the label prop changes', () => {
230+
const { rerender } = render(<CopyableInput {...defaultProps} />);
231+
rerender(<CopyableInput {...defaultProps} label="New Label" />);
232+
expect(screen.getByText('New Label')).toBeInTheDocument();
233+
});
234+
});
235+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './CopyableInput';

0 commit comments

Comments
 (0)