Skip to content

Commit a0385ff

Browse files
committed
test: enhance InLink component test coverage and refactor class handling logic
- Added tests for className updates under various scenarios, including prop changes and combinations. - Refactored className logic in `InLink` to properly handle custom classes without default styles. - Replaced `<a>` tags with `InLink` in ISBD pages for consistent styling and functionality. - Enhanced className memoization for optimized re-rendering in `InLink`. - Adjusted tests for smartWrap behavior and preserved accessibility compliance checks.
1 parent 6a94abc commit a0385ff

File tree

4 files changed

+202
-97
lines changed

4 files changed

+202
-97
lines changed

packages/theme/src/components/InLink/index.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ export interface InLinkProps {
99
* URL to link to - will be processed through useBaseUrl
1010
*/
1111
href: string;
12-
12+
1313
/**
1414
* Link content
1515
*/
1616
children: React.ReactNode;
17-
17+
1818
/**
1919
* CSS class name
2020
*/
2121
className?: string;
22-
22+
2323
/**
2424
* Enable smart wrapping before parentheses
2525
* @default true
@@ -69,27 +69,33 @@ export const InLink: React.FC<InLinkProps> = ({
6969
}) => {
7070
// Process URL through useBaseUrl
7171
const processedHref = useBaseUrl(href);
72-
72+
7373
// Process children for smart wrapping if enabled
7474
const processedChildren = useMemo(() => {
7575
if (smartWrap) {
7676
return processChildrenForSmartWrap(children);
7777
}
7878
return children;
7979
}, [children, smartWrap]);
80-
80+
81+
// Memoize className computation to ensure proper re-rendering
82+
const computedClassName = useMemo(() => {
83+
// If custom className is provided, use only that (no default styles)
84+
if (className) {
85+
return className;
86+
}
87+
// Otherwise, apply default IFLA link styling
88+
return clsx('linkInline', styles.inLink);
89+
}, [className]);
90+
8191
return (
8292
<Link
8393
to={processedHref}
84-
className={clsx(
85-
'linkInline',
86-
styles.inLink,
87-
className
88-
)}
94+
className={computedClassName}
8995
>
9096
{processedChildren}
9197
</Link>
9298
);
9399
};
94100

95-
export default InLink;
101+
export default InLink;

packages/theme/src/tests/components/InLink-improved.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ describe('InLink Component - Real Functionality Tests', () => {
265265
const link = container.querySelector('a');
266266
const classes = link?.className.split(' ') || [];
267267

268-
// Should have base class and additional class
269-
expect(classes.length).toBeGreaterThanOrEqual(2);
268+
// With custom className, should only have that class (no default styles)
269+
expect(classes.length).toBe(1);
270270
expect(classes).toContain('additional-class');
271271
});
272272

packages/theme/src/tests/components/InLink.test.tsx

Lines changed: 128 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,54 +27,158 @@ describe('InLink Component', () => {
2727
has extent of embodied content
2828
</InLink>
2929
);
30-
30+
3131
const link = screen.getByText('has extent of embodied content');
3232
expect(link).toBeInTheDocument();
3333
expect(link).toHaveAttribute('href', '/docs/attributes/1277.html');
3434
expect(link).toHaveClass('linkInline');
3535
});
36-
36+
3737
it('processes URLs with useBaseUrl', () => {
3838
render(
3939
<InLink href="docs/attributes/1277.html">
4040
has extent of embodied content
4141
</InLink>
4242
);
43-
43+
4444
const link = screen.getByText('has extent of embodied content');
4545
expect(link).toHaveAttribute('href', '/docs/attributes/1277.html');
4646
});
47-
48-
it('applies additional className when provided', () => {
47+
48+
it('applies only custom className when provided (no default styles)', () => {
4949
render(
5050
<InLink href="/docs/attributes/1277.html" className="custom-class">
5151
has extent of embodied content
5252
</InLink>
5353
);
54-
54+
5555
const link = screen.getByText('has extent of embodied content');
5656
expect(link).toHaveClass('custom-class');
57+
expect(link).not.toHaveClass('linkInline'); // linkInline should NOT be present with custom class
58+
expect(link?.className).not.toMatch(/inLink/); // CSS module class should not be applied
59+
});
60+
61+
it('updates rendered class when className prop changes', () => {
62+
const TestComponent = ({ className }: { className: string }) => (
63+
<InLink href="/docs/test" className={className}>
64+
Test Link
65+
</InLink>
66+
);
67+
68+
const { rerender } = render(<TestComponent className="initial-class" />);
69+
70+
const link = screen.getByText('Test Link');
71+
expect(link).toHaveClass('initial-class');
72+
expect(link).not.toHaveClass('updated-class');
73+
74+
// Change the className prop
75+
rerender(<TestComponent className="updated-class" />);
76+
77+
// The component should re-render with the new class
78+
expect(link).toHaveClass('updated-class');
79+
expect(link).not.toHaveClass('initial-class');
80+
});
81+
82+
it('updates className when multiple props change simultaneously', () => {
83+
const TestComponent = ({ className, href, children }: { className: string; href: string; children: string }) => (
84+
<InLink href={href} className={className}>
85+
{children}
86+
</InLink>
87+
);
88+
89+
const { rerender } = render(
90+
<TestComponent className="class-one" href="/docs/test1" children="Link One" />
91+
);
92+
93+
let link = screen.getByText('Link One');
94+
expect(link).toHaveClass('class-one');
95+
expect(link).toHaveAttribute('href', '/docs/test1');
96+
97+
// Change multiple props including className
98+
rerender(
99+
<TestComponent className="class-two" href="/docs/test2" children="Link Two" />
100+
);
101+
102+
link = screen.getByText('Link Two');
103+
expect(link).toHaveClass('class-two');
104+
expect(link).not.toHaveClass('class-one');
105+
expect(link).toHaveAttribute('href', '/docs/test2');
106+
});
107+
108+
it('handles className changes with smartWrap enabled and disabled', () => {
109+
const TestComponent = ({ className, smartWrap }: { className: string; smartWrap: boolean }) => (
110+
<InLink href="/docs/test" className={className} smartWrap={smartWrap}>
111+
Text with (parentheses)
112+
</InLink>
113+
);
114+
115+
const { rerender } = render(
116+
<TestComponent className="smart-wrap-class" smartWrap={true} />
117+
);
118+
119+
const link = screen.getByText(/Text with.*parentheses/);
120+
expect(link).toHaveClass('smart-wrap-class');
121+
expect(link?.className).not.toMatch(/inLink/); // CSS module class not applied with custom className
122+
123+
// Change className and smartWrap
124+
rerender(
125+
<TestComponent className="no-wrap-class" smartWrap={false} />
126+
);
127+
128+
expect(link).toHaveClass('no-wrap-class');
129+
expect(link).not.toHaveClass('smart-wrap-class');
130+
expect(link?.className).not.toMatch(/inLink/); // CSS module class still not applied
131+
});
132+
133+
it('properly manages default vs custom classes when className changes', () => {
134+
const TestComponent = ({ className }: { className?: string }) => (
135+
<InLink href="/docs/test" className={className}>
136+
Test Link
137+
</InLink>
138+
);
139+
140+
const { rerender } = render(<TestComponent className="custom-class" />);
141+
142+
const link = screen.getByText('Test Link');
143+
expect(link).not.toHaveClass('linkInline'); // NOT present with custom class
144+
expect(link).toHaveClass('custom-class');
145+
expect(link?.className).not.toMatch(/inLink/); // CSS module class not applied
146+
147+
// Change to different className
148+
rerender(<TestComponent className="another-class" />);
149+
150+
expect(link).not.toHaveClass('linkInline'); // Still not present
151+
expect(link).toHaveClass('another-class');
152+
expect(link).not.toHaveClass('custom-class');
153+
expect(link?.className).not.toMatch(/inLink/); // CSS module class still not applied
154+
155+
// Remove custom className - default styles should now apply
156+
rerender(<TestComponent />);
157+
158+
expect(link).toHaveClass('linkInline'); // Now present
159+
expect(link?.className).toMatch(/inLink/); // CSS module class should now be applied
160+
expect(link).not.toHaveClass('another-class');
57161
});
58-
162+
59163
it('renders with complex children', () => {
60164
render(
61165
<InLink href="/docs/attributes/1277.html">
62166
<span data-testid="child-element">Complex child</span>
63167
</InLink>
64168
);
65-
169+
66170
const childElement = screen.getByTestId('child-element');
67171
expect(childElement).toBeInTheDocument();
68172
expect(childElement.textContent).toBe('Complex child');
69173
});
70-
174+
71175
it('preserves href with query parameters', () => {
72176
render(
73177
<InLink href="/docs/attributes/1277.html?param=value#section">
74178
Link with query and hash
75179
</InLink>
76180
);
77-
181+
78182
const link = screen.getByText('Link with query and hash');
79183
expect(link).toHaveAttribute('href', '/docs/attributes/1277.html?param=value#section');
80184
});
@@ -86,7 +190,7 @@ describe('InLink Component', () => {
86190
Catalogue of shipwrecked books (2018; William Collins; volume; case binding)
87191
</InLink>
88192
);
89-
193+
90194
const link = container.querySelector('a');
91195
// The text should contain zero-width spaces before parentheses
92196
expect(link?.textContent).toContain('\u200B(');
@@ -102,7 +206,7 @@ describe('InLink Component', () => {
102206
Example text (first) and more (second) content
103207
</InLink>
104208
);
105-
209+
106210
const link = container.querySelector('a');
107211
const textContent = link?.textContent || '';
108212
// Count zero-width spaces - should be 2 (one before each parenthesis)
@@ -116,7 +220,7 @@ describe('InLink Component', () => {
116220
<span>Text with (parentheses) inside</span>
117221
</InLink>
118222
);
119-
223+
120224
const span = container.querySelector('span');
121225
expect(span?.textContent).toContain('\u200B(');
122226
});
@@ -127,7 +231,7 @@ describe('InLink Component', () => {
127231
Text with (parentheses) inside
128232
</InLink>
129233
);
130-
234+
131235
const link = container.querySelector('a');
132236
expect(link?.textContent).not.toContain('\u200B');
133237
expect(link?.textContent).toBe('Text with (parentheses) inside');
@@ -139,7 +243,7 @@ describe('InLink Component', () => {
139243
Text without any parentheses
140244
</InLink>
141245
);
142-
246+
143247
const link = container.querySelector('a');
144248
expect(link?.textContent).toBe('Text without any parentheses');
145249
expect(link?.textContent).not.toContain('\u200B');
@@ -151,7 +255,7 @@ describe('InLink Component', () => {
151255
Text with empty () parentheses
152256
</InLink>
153257
);
154-
258+
155259
const link = container.querySelector('a');
156260
expect(link?.textContent).toBe('Text with empty \u200B() parentheses');
157261
});
@@ -162,36 +266,37 @@ describe('InLink Component', () => {
162266
Start text (with parens) <em>emphasized (text)</em> end
163267
</InLink>
164268
);
165-
269+
166270
const link = container.querySelector('a');
167271
const textContent = link?.textContent || '';
168272
// Should have 2 zero-width spaces
169273
const zwsCount = (textContent.match(/\u200B/g) || []).length;
170274
expect(zwsCount).toBe(2);
171-
275+
172276
// Check that emphasized text is preserved
173277
const em = container.querySelector('em');
174278
expect(em).toBeInTheDocument();
175279
expect(em?.textContent).toContain('\u200B(');
176280
});
177281
});
178282

179-
it('applies correct CSS module classes', () => {
283+
it('applies correct CSS module classes when no custom className provided', () => {
180284
const { container } = render(
181285
<InLink href="/test">Test Link</InLink>
182286
);
183287

184288
const link = container.querySelector('a');
185289
expect(link?.className).toMatch(/inLink/);
290+
expect(link).toHaveClass('linkInline');
186291
});
187-
292+
188293
it('passes accessibility tests', async () => {
189294
const { container } = render(
190295
<InLink href="/docs/attributes/1277.html">
191296
Accessible Link
192297
</InLink>
193298
);
194-
299+
195300
const results = await axe(container);
196301
expect(results).toHaveNoViolations();
197302
});
@@ -202,8 +307,8 @@ describe('InLink Component', () => {
202307
Catalogue of shipwrecked books (2018; William Collins; volume; case binding)
203308
</InLink>
204309
);
205-
310+
206311
const results = await axe(container);
207312
expect(results).toHaveNoViolations();
208313
});
209-
});
314+
});

0 commit comments

Comments
 (0)