Skip to content

Commit 3cd0341

Browse files
committed
test: All test
1 parent 5e7b03c commit 3cd0341

File tree

6 files changed

+152
-109
lines changed

6 files changed

+152
-109
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
4141
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
4242
"pretty-quick": "pretty-quick",
43-
"test": "father test",
44-
"coverage": "father test --coverage"
43+
"test": "umi-test",
44+
"coverage": "umi-test --coverage"
4545
},
4646
"dependencies": {
4747
"@babel/runtime": "^7.11.1",
@@ -57,8 +57,10 @@
5757
"@types/react": "^17.0.13",
5858
"@types/react-dom": "^16.9.0",
5959
"@umijs/fabric": "^2.0.8",
60+
"@umijs/test": "^3.5.23",
6061
"coveralls": "^3.0.6",
6162
"cross-env": "^7.0.2",
63+
"cssstyle": "^2.3.0",
6264
"dumi": "^1.1.41-rc.0",
6365
"eslint": "^7.0.0",
6466
"father": "^2.13.4",

src/MotionThumb.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -116,21 +116,26 @@ export default function MotionThumb(props: MotionThumbInterface) {
116116
onAppearEnd={onAppearEnd}
117117
>
118118
{({ className: motionClassName, style: motionStyle }, ref) => {
119-
return (
120-
<div
121-
ref={composeRef(thumbRef, ref)}
122-
style={
123-
{
124-
...motionStyle,
125-
'--thumb-start-left': toPX(prevStyle?.left),
126-
'--thumb-start-width': toPX(prevStyle?.width),
127-
'--thumb-active-left': toPX(nextStyle?.left),
128-
'--thumb-active-width': toPX(nextStyle?.width),
129-
} as React.CSSProperties
130-
}
131-
className={classNames(`${prefixCls}-thumb`, motionClassName)}
132-
/>
133-
);
119+
const mergedStyle = {
120+
...motionStyle,
121+
'--thumb-start-left': toPX(prevStyle?.left),
122+
'--thumb-start-width': toPX(prevStyle?.width),
123+
'--thumb-active-left': toPX(nextStyle?.left),
124+
'--thumb-active-width': toPX(nextStyle?.width),
125+
} as React.CSSProperties;
126+
127+
// It's little ugly which should be refactor when @umi/test update to latest jsdom
128+
const motionProps = {
129+
ref: composeRef(thumbRef, ref),
130+
style: mergedStyle,
131+
className: classNames(`${prefixCls}-thumb`, motionClassName),
132+
};
133+
134+
if (process.env.NODE_ENV === 'test') {
135+
(motionProps as any)['data-test-style'] = JSON.stringify(mergedStyle);
136+
}
137+
138+
return <div {...motionProps} />;
134139
}}
135140
</CSSMotion>
136141
);

src/index.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ function normalizeOptions(options: SegmentedOptions): SegmentedLabeledOption[] {
6565
});
6666
}
6767

68-
// const calcThumbStyle = (targetElement: HTMLElement): React.CSSProperties => ({
69-
// transform: `translateX(${targetElement.offsetLeft}px)`,
70-
// width: targetElement.clientWidth,
71-
// });
72-
7368
const InternalSegmentedOption: React.FC<{
7469
prefixCls: string;
7570
className?: string;
@@ -142,11 +137,14 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
142137
return normalizeOptions(options);
143138
}, [options]);
144139

145-
const [selected, setSelected] = useMergedState(segmentedOptions[0]?.value, {
146-
value: props.value,
140+
// Note: We should not auto switch value when value not exist in options
141+
// which may break single source of truth.
142+
const [rawValue, setRawValue] = useMergedState(segmentedOptions[0]?.value, {
143+
value,
147144
defaultValue,
148145
});
149146

147+
// ======================= Change ========================
150148
const [thumbShow, setThumbShow] = React.useState(false);
151149

152150
const handleChange = (
@@ -157,7 +155,7 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
157155
return;
158156
}
159157

160-
setSelected(val);
158+
setRawValue(val);
161159

162160
onChange?.(val);
163161
};
@@ -179,7 +177,7 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
179177
>
180178
<MotionThumb
181179
prefixCls={prefixCls}
182-
value={selected}
180+
value={rawValue}
183181
containerRef={containerRef}
184182
motionName={`${prefixCls}-${motionName}`}
185183
getValueIndex={(val) =>
@@ -201,10 +199,10 @@ const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
201199
`${prefixCls}-item`,
202200
{
203201
[`${prefixCls}-item-selected`]:
204-
segmentedOption.value === value && !thumbShow,
202+
segmentedOption.value === rawValue && !thumbShow,
205203
},
206204
)}
207-
checked={segmentedOption.value === selected}
205+
checked={segmentedOption.value === rawValue}
208206
onChange={handleChange}
209207
{...segmentedOption}
210208
/>

tests/__snapshots__/index.spec.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ exports[`rc-segmented render segmented ok 1`] = `
110110
</div>
111111
`;
112112

113-
exports[`rc-segmented render segmented with CSSMotion 1`] = `
113+
exports[`rc-segmented render segmented with CSSMotion basic 1`] = `
114114
<div
115115
class="rc-segmented"
116116
>

tests/index.spec.tsx

Lines changed: 115 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import { render, act, fireEvent } from '@testing-library/react';
33
import Segmented from '../src';
4-
import type { SegmentedValue } from '../src';
54

65
jest.mock('rc-motion/lib/util/motion', () => {
76
return {
@@ -24,6 +23,15 @@ describe('rc-segmented', () => {
2423
});
2524
}
2625

26+
function exceptThumbHaveStyle(container: HTMLElement, matchStyle: object) {
27+
const styleText = container
28+
.querySelector('.rc-segmented-thumb')
29+
?.getAttribute('data-test-style');
30+
const style = JSON.parse(styleText!) || {};
31+
32+
expect(style).toMatchObject(matchStyle);
33+
}
34+
2735
beforeEach(() => {
2836
jest.useFakeTimers();
2937
});
@@ -260,105 +268,133 @@ describe('rc-segmented', () => {
260268
container.querySelector('.rc-segmented-item-selected')?.textContent,
261269
).toContain('Web3');
262270

263-
// Motion end
264-
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
265-
act(() => {
266-
jest.runAllTimers();
267-
});
268-
269271
// change it strangely
270272
fireEvent.change(container.querySelector('.control')!, {
271273
target: { value: 'Web4' },
272274
});
273275

274-
// invalid changes
275-
expect(
276-
container.querySelector('.rc-segmented-item-selected')?.textContent,
277-
).toContain('Web3');
276+
// invalid changes: Should not active any item to make sure it's single source of truth
277+
expect(container.querySelector('.rc-segmented-item-selected')).toBeFalsy();
278278
});
279279

280-
it('render segmented with CSSMotion', () => {
281-
const handleValueChange = jest.fn();
282-
const { container, asFragment } = render(
283-
<Segmented
284-
options={['iOS', 'Android', 'Web3']}
285-
onChange={(value) => handleValueChange(value)}
286-
/>,
287-
);
288-
expect(asFragment().firstChild).toMatchSnapshot();
289-
290-
expectMatchChecked(container, [true, false, false]);
291-
expect(container.querySelectorAll('.rc-segmented-item')[0]).toHaveClass(
292-
'rc-segmented-item-selected',
293-
);
280+
describe('render segmented with CSSMotion', () => {
281+
it('basic', () => {
282+
const handleValueChange = jest.fn();
283+
const { container, asFragment } = render(
284+
<Segmented
285+
options={['iOS', 'Android', 'Web3']}
286+
onChange={(value) => handleValueChange(value)}
287+
/>,
288+
);
289+
expect(asFragment().firstChild).toMatchSnapshot();
294290

295-
fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[2]);
296-
expect(handleValueChange).toBeCalledWith('Web3');
297-
expectMatchChecked(container, [false, false, true]);
291+
expectMatchChecked(container, [true, false, false]);
292+
expect(container.querySelectorAll('.rc-segmented-item')[0]).toHaveClass(
293+
'rc-segmented-item-selected',
294+
);
298295

299-
expect(container.querySelectorAll('.rc-segmented-thumb')[0]).toHaveClass(
300-
'rc-segmented-thumb-motion',
301-
);
296+
// >>> Click: Web3
297+
fireEvent.click(
298+
container.querySelectorAll('.rc-segmented-item-input')[2],
299+
);
300+
expect(handleValueChange).toBeCalledWith('Web3');
301+
expectMatchChecked(container, [false, false, true]);
302302

303-
// thumb appeared at `iOS`
304-
expect(container.querySelectorAll('.rc-segmented-thumb')[0]).toHaveStyle({
305-
transform: 'translateX(0px)',
306-
width: '62px',
307-
});
303+
expect(container.querySelectorAll('.rc-segmented-thumb')[0]).toHaveClass(
304+
'rc-segmented-thumb-motion',
305+
);
308306

309-
// Motion => active
310-
act(() => {
311-
jest.runAllTimers();
312-
});
307+
// thumb appeared at `iOS`
308+
exceptThumbHaveStyle(container, {
309+
'--thumb-start-left': '0px',
310+
'--thumb-start-width': '62px',
311+
});
312+
313+
// Motion => active
314+
act(() => {
315+
jest.runAllTimers();
316+
});
317+
318+
// Motion enter end
319+
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
320+
act(() => {
321+
jest.runAllTimers();
322+
});
323+
324+
// thumb should disappear
325+
expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy();
326+
327+
// >>> Click: Android
328+
fireEvent.click(
329+
container.querySelectorAll('.rc-segmented-item-input')[1],
330+
);
331+
expect(handleValueChange).toBeCalledWith('Android');
332+
expectMatchChecked(container, [false, true, false]);
313333

314-
// Motion enter end
315-
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
316-
act(() => {
317-
jest.runAllTimers();
318-
});
334+
// thumb should move
335+
expect(container.querySelector('.rc-segmented-thumb')).toHaveClass(
336+
'rc-segmented-thumb-motion',
337+
);
319338

320-
// Motion leave end
321-
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
322-
act(() => {
323-
jest.runAllTimers();
339+
// thumb appeared at `Web3`
340+
exceptThumbHaveStyle(container, {
341+
'--thumb-start-left': '180px',
342+
'--thumb-start-width': '76px',
343+
});
344+
345+
// Motion appear end
346+
act(() => {
347+
jest.runAllTimers();
348+
});
349+
exceptThumbHaveStyle(container, {
350+
'--thumb-active-left': '62px',
351+
'--thumb-active-width': '118px',
352+
});
353+
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
354+
act(() => {
355+
jest.runAllTimers();
356+
});
357+
358+
// thumb should disappear
359+
expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy();
324360
});
325361

326-
// thumb should disappear
327-
expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy();
362+
it('quick switch', () => {
363+
const { container } = render(
364+
<Segmented
365+
options={['IOS', 'Android', 'Web3']}
366+
defaultValue="Android"
367+
/>,
368+
);
328369

329-
// change selection again
330-
fireEvent.click(container.querySelectorAll('.rc-segmented-item-input')[1]);
331-
expect(handleValueChange).toBeCalledWith('Android');
332-
expectMatchChecked(container, [false, true, false]);
370+
// >>> Click: Web3
371+
fireEvent.click(
372+
container.querySelectorAll('.rc-segmented-item-input')[2],
373+
);
333374

334-
// thumb should move
335-
expect(container.querySelector('.rc-segmented-thumb')).toHaveClass(
336-
'rc-segmented-thumb-motion',
337-
);
375+
// Motion to active
376+
act(() => {
377+
jest.runAllTimers();
378+
});
379+
expect(container.querySelector('.rc-segmented-thumb')).toHaveClass(
380+
'rc-segmented-thumb-motion-appear-active',
381+
);
338382

339-
// thumb appeared at `Web3`
340-
expect(container.querySelector('.rc-segmented-thumb')).toHaveStyle({
341-
transform: 'translateX(180px)',
342-
width: '76px',
343-
});
383+
exceptThumbHaveStyle(container, {
384+
'--thumb-active-left': '180px',
385+
'--thumb-active-width': '76px',
386+
});
344387

345-
// Motion enter end
346-
act(() => {
347-
jest.runAllTimers();
348-
});
349-
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
350-
act(() => {
351-
jest.runAllTimers();
352-
});
388+
// >>> Click: IOS
389+
fireEvent.click(
390+
container.querySelectorAll('.rc-segmented-item-input')[0],
391+
);
353392

354-
// Motion leave end
355-
fireEvent.animationEnd(container.querySelector('.rc-segmented-thumb')!);
356-
act(() => {
357-
jest.runAllTimers();
393+
exceptThumbHaveStyle(container, {
394+
'--thumb-active-left': '0px',
395+
'--thumb-active-width': '62px',
396+
});
358397
});
359-
360-
// thumb should disappear
361-
expect(container.querySelector('.rc-segmented-thumb')).toBeFalsy();
362398
});
363399

364400
it('render segmented with options null/undefined', () => {

tests/setup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ Object.defineProperties(window.HTMLElement.prototype, {
1313
offsetLeft: {
1414
get() {
1515
let offsetLeft = 0;
16-
const childList: HTMLElement[] = Array.from(this.parentNode.children);
16+
const childList: HTMLElement[] = Array.from(
17+
(this.parentNode as HTMLElement).querySelectorAll('.rc-segmented-item'),
18+
);
1719
for (let i = 0; i < childList.length; i++) {
1820
const child = childList[i];
1921
const lastChild = childList[i - 1];

0 commit comments

Comments
 (0)