@transferwise/components 0.0.0-experimental-c91775b → 0.0.0-experimental-b34fee0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/flowNavigation/FlowNavigation.js +55 -41
- package/build/flowNavigation/FlowNavigation.js.map +1 -1
- package/build/flowNavigation/FlowNavigation.mjs +55 -41
- package/build/flowNavigation/FlowNavigation.mjs.map +1 -1
- package/build/main.css +18 -2
- package/build/overlayHeader/OverlayHeader.js +3 -0
- package/build/overlayHeader/OverlayHeader.js.map +1 -1
- package/build/overlayHeader/OverlayHeader.mjs +3 -0
- package/build/overlayHeader/OverlayHeader.mjs.map +1 -1
- package/build/styles/flowNavigation/FlowNavigation.css +15 -2
- package/build/styles/main.css +18 -2
- package/build/types/flowNavigation/FlowNavigation.d.ts +14 -1
- package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
- package/build/types/overlayHeader/OverlayHeader.d.ts +6 -0
- package/build/types/overlayHeader/OverlayHeader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/flowNavigation/FlowNavigation.css +15 -2
- package/src/flowNavigation/FlowNavigation.less +19 -4
- package/src/flowNavigation/FlowNavigation.story.tsx +240 -0
- package/src/flowNavigation/FlowNavigation.test.tsx +345 -0
- package/src/flowNavigation/FlowNavigation.tsx +89 -53
- package/src/main.css +18 -2
- package/src/overlayHeader/OverlayHeader.story.tsx +4 -0
- package/src/overlayHeader/OverlayHeader.tsx +6 -0
- package/src/flowNavigation/FlowNavigation.test.js +0 -190
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
@import (reference) "./../styles/less/addons/_spacing-utilities.less";
|
|
3
3
|
|
|
4
4
|
.np-flow-navigation {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
5
8
|
width: 100%;
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
// This is the natural height of FlowNavigation when the Avatar is displayed.
|
|
11
|
+
min-height: 96px;
|
|
8
12
|
|
|
9
13
|
&--border-bottom {
|
|
10
|
-
// This is the natural height of FlowNavigation when the Avatar is displayed.
|
|
11
|
-
min-height: 96px;
|
|
12
14
|
border-bottom: 1px solid var(--color-border-neutral);
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -66,6 +68,19 @@
|
|
|
66
68
|
visibility: hidden;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
&--composable {
|
|
72
|
+
min-height: 80px;
|
|
73
|
+
|
|
74
|
+
@media (--screen-xs) {
|
|
75
|
+
min-height: 128px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.np-flow-navigation__content {
|
|
79
|
+
// Remove max-width constraint for fluid layout
|
|
80
|
+
max-width: none;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
.np-theme-personal--forest-green &,
|
|
70
85
|
.np-theme-personal--bright-green &,
|
|
71
86
|
.np-theme-personal--dark & {
|
|
@@ -25,6 +25,13 @@ export type StoryArgs = FlowNavigationProps & CustomControls;
|
|
|
25
25
|
|
|
26
26
|
type Story = StoryObj<StoryArgs>;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* FlowNavigation component for multi-step flows with progress tracking.
|
|
30
|
+
*
|
|
31
|
+
* **Border Behavior:**
|
|
32
|
+
* - Non-composable (default): Border respects `done` prop when `showBottomBorder=true`.
|
|
33
|
+
* - Composable: Border controlled only by `showBottomBorder`, ignoring the `done` property.
|
|
34
|
+
**/
|
|
28
35
|
const meta: Meta<StoryArgs> = {
|
|
29
36
|
component: FlowNavigation,
|
|
30
37
|
title: 'Navigation/FlowNavigation',
|
|
@@ -32,6 +39,7 @@ const meta: Meta<StoryArgs> = {
|
|
|
32
39
|
showCloseButton: { control: 'boolean', defaultValue: true },
|
|
33
40
|
showMobileBackButton: { control: 'boolean', defaultValue: true },
|
|
34
41
|
done: { control: 'boolean', defaultValue: false },
|
|
42
|
+
showBottomBorder: { control: 'boolean', defaultValue: true },
|
|
35
43
|
avatarURL: { control: 'text', defaultValue: '../tapestry-01.png' },
|
|
36
44
|
profileType: {
|
|
37
45
|
control: 'radio',
|
|
@@ -372,3 +380,235 @@ export const WithOverlayHeaderComparison: Story = {
|
|
|
372
380
|
);
|
|
373
381
|
},
|
|
374
382
|
};
|
|
383
|
+
|
|
384
|
+
export const ComposableVariant: Story = {
|
|
385
|
+
args: {
|
|
386
|
+
profileType: ProfileType.PERSONAL,
|
|
387
|
+
showCloseButton: true,
|
|
388
|
+
showMobileBackButton: true,
|
|
389
|
+
done: false,
|
|
390
|
+
avatarURL: '../tapestry-01.png',
|
|
391
|
+
},
|
|
392
|
+
parameters: {
|
|
393
|
+
padding: '0',
|
|
394
|
+
chromatic: {
|
|
395
|
+
viewports,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
render: (args) => {
|
|
399
|
+
const [activeStep, setActiveStep] = useState(2);
|
|
400
|
+
const steps = [
|
|
401
|
+
{
|
|
402
|
+
label: 'Recipient',
|
|
403
|
+
onClick: () => setActiveStep(0),
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
label: 'Amount',
|
|
407
|
+
hoverLabel: (
|
|
408
|
+
<>
|
|
409
|
+
You send 100 GBP <br />
|
|
410
|
+
You get 99.39 GBP{' '}
|
|
411
|
+
</>
|
|
412
|
+
),
|
|
413
|
+
...(activeStep > 1 && { onClick: () => setActiveStep(1) }),
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
label: 'Review',
|
|
417
|
+
...(activeStep > 2 && { onClick: () => setActiveStep(2) }),
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
label: 'Pay',
|
|
421
|
+
...(activeStep > 3 && { onClick: () => setActiveStep(3) }),
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
return (
|
|
425
|
+
<>
|
|
426
|
+
<FlowNavigation
|
|
427
|
+
composable
|
|
428
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
429
|
+
logo={<Logo />}
|
|
430
|
+
activeStep={activeStep}
|
|
431
|
+
steps={steps}
|
|
432
|
+
onClose={args.showCloseButton ? () => alert('close') : undefined}
|
|
433
|
+
onGoBack={
|
|
434
|
+
args.showMobileBackButton
|
|
435
|
+
? () => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)
|
|
436
|
+
: undefined
|
|
437
|
+
}
|
|
438
|
+
/>
|
|
439
|
+
<Container size="narrow">
|
|
440
|
+
<Body className="m-a-3">
|
|
441
|
+
<Display type={Typography.DISPLAY_SMALL}>{steps[activeStep].label} Step</Display>
|
|
442
|
+
<br />
|
|
443
|
+
{lorem10}
|
|
444
|
+
</Body>
|
|
445
|
+
</Container>
|
|
446
|
+
|
|
447
|
+
<Sticky>
|
|
448
|
+
<Container
|
|
449
|
+
size="narrow"
|
|
450
|
+
className="d-flex justify-content-center align-items-center p-y-3"
|
|
451
|
+
>
|
|
452
|
+
<Button
|
|
453
|
+
v2
|
|
454
|
+
disabled={activeStep === 3}
|
|
455
|
+
block
|
|
456
|
+
onClick={() => setActiveStep(activeStep + 1)}
|
|
457
|
+
>
|
|
458
|
+
Continue
|
|
459
|
+
</Button>
|
|
460
|
+
</Container>
|
|
461
|
+
</Sticky>
|
|
462
|
+
</>
|
|
463
|
+
);
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export const BorderControl: Story = {
|
|
468
|
+
args: {
|
|
469
|
+
profileType: ProfileType.PERSONAL,
|
|
470
|
+
avatarURL: '../tapestry-01.png',
|
|
471
|
+
},
|
|
472
|
+
parameters: {
|
|
473
|
+
chromatic: {
|
|
474
|
+
viewports,
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
render: (args) => {
|
|
478
|
+
const [activeStep, setActiveStep] = useState(2);
|
|
479
|
+
|
|
480
|
+
const steps = [
|
|
481
|
+
{
|
|
482
|
+
label: 'Recipient',
|
|
483
|
+
onClick: () => setActiveStep(0),
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
label: 'Amount',
|
|
487
|
+
hoverLabel: (
|
|
488
|
+
<>
|
|
489
|
+
You send 100 GBP <br />
|
|
490
|
+
You get 99.39 GBP{' '}
|
|
491
|
+
</>
|
|
492
|
+
),
|
|
493
|
+
...(activeStep > 1 && { onClick: () => setActiveStep(1) }),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
label: 'Review',
|
|
497
|
+
...(activeStep > 2 && { onClick: () => setActiveStep(2) }),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
label: 'Pay',
|
|
501
|
+
...(activeStep > 3 && { onClick: () => setActiveStep(3) }),
|
|
502
|
+
},
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<>
|
|
507
|
+
<Body className="text-xs-center m-b-3">
|
|
508
|
+
<Display type={Typography.DISPLAY_SMALL}>Non-composable Variant</Display>
|
|
509
|
+
<br />
|
|
510
|
+
Border follows <code>done</code> when <code>showBottomBorder=true</code>
|
|
511
|
+
</Body>
|
|
512
|
+
|
|
513
|
+
{/* Non-composable: showBottomBorder=true, done=false - SHOWS BORDER */}
|
|
514
|
+
<FlowNavigation
|
|
515
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
516
|
+
logo={<Logo />}
|
|
517
|
+
activeStep={activeStep}
|
|
518
|
+
done={false}
|
|
519
|
+
steps={steps}
|
|
520
|
+
showBottomBorder
|
|
521
|
+
onClose={() => {}}
|
|
522
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
523
|
+
/>
|
|
524
|
+
<Body className="text-xs-center m-b-3">
|
|
525
|
+
<code>showBottomBorder=true, done=false</code> → ✅ Border shown
|
|
526
|
+
</Body>
|
|
527
|
+
|
|
528
|
+
{/* Non-composable: showBottomBorder=true, done=true - HIDES BORDER */}
|
|
529
|
+
<FlowNavigation
|
|
530
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
531
|
+
logo={<Logo />}
|
|
532
|
+
activeStep={activeStep}
|
|
533
|
+
done
|
|
534
|
+
steps={steps}
|
|
535
|
+
showBottomBorder
|
|
536
|
+
onClose={() => {}}
|
|
537
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
538
|
+
/>
|
|
539
|
+
<Body className="text-xs-center m-b-3">
|
|
540
|
+
<code>showBottomBorder=true, done=true</code> → ❌ Border hidden
|
|
541
|
+
</Body>
|
|
542
|
+
|
|
543
|
+
{/* Non-composable: showBottomBorder=false, done=false - HIDES BORDER */}
|
|
544
|
+
<FlowNavigation
|
|
545
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
546
|
+
logo={<Logo />}
|
|
547
|
+
activeStep={activeStep}
|
|
548
|
+
done={false}
|
|
549
|
+
steps={steps}
|
|
550
|
+
showBottomBorder={false}
|
|
551
|
+
onClose={() => {}}
|
|
552
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
553
|
+
/>
|
|
554
|
+
<Body className="text-xs-center m-b-5">
|
|
555
|
+
<code>showBottomBorder=false, done=false</code> → ❌ Border hidden
|
|
556
|
+
</Body>
|
|
557
|
+
|
|
558
|
+
<Body className="text-xs-center m-b-3 m-t-5">
|
|
559
|
+
<Display type={Typography.DISPLAY_SMALL}>Composable Variant</Display>
|
|
560
|
+
<br />
|
|
561
|
+
Border ignores <code>done</code>, follows <code>showBottomBorder</code> only
|
|
562
|
+
</Body>
|
|
563
|
+
|
|
564
|
+
{/* Composable: showBottomBorder=true, done=false - SHOWS BORDER */}
|
|
565
|
+
<FlowNavigation
|
|
566
|
+
composable
|
|
567
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
568
|
+
logo={<Logo />}
|
|
569
|
+
activeStep={activeStep}
|
|
570
|
+
done={false}
|
|
571
|
+
steps={steps}
|
|
572
|
+
showBottomBorder
|
|
573
|
+
onClose={() => {}}
|
|
574
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
575
|
+
/>
|
|
576
|
+
<Body className="text-xs-center m-b-3">
|
|
577
|
+
<code>showBottomBorder=true, done=false</code> → ✅ Border shown
|
|
578
|
+
</Body>
|
|
579
|
+
|
|
580
|
+
{/* Composable: showBottomBorder=true, done=true - SHOWS BORDER */}
|
|
581
|
+
<FlowNavigation
|
|
582
|
+
composable
|
|
583
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
584
|
+
logo={<Logo />}
|
|
585
|
+
activeStep={activeStep}
|
|
586
|
+
done
|
|
587
|
+
steps={steps}
|
|
588
|
+
showBottomBorder
|
|
589
|
+
onClose={() => {}}
|
|
590
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
591
|
+
/>
|
|
592
|
+
<Body className="text-xs-center m-b-3">
|
|
593
|
+
<code>showBottomBorder=true, done=true</code> → ✅ Border shown (ignores done)
|
|
594
|
+
</Body>
|
|
595
|
+
|
|
596
|
+
{/* Composable: showBottomBorder=false, done=false - HIDES BORDER */}
|
|
597
|
+
<FlowNavigation
|
|
598
|
+
composable
|
|
599
|
+
avatar={<AvatarView profileType={args.profileType} />}
|
|
600
|
+
logo={<Logo />}
|
|
601
|
+
activeStep={activeStep}
|
|
602
|
+
done={false}
|
|
603
|
+
steps={steps}
|
|
604
|
+
showBottomBorder={false}
|
|
605
|
+
onClose={() => {}}
|
|
606
|
+
onGoBack={() => setActiveStep(activeStep > 0 ? activeStep - 1 : 0)}
|
|
607
|
+
/>
|
|
608
|
+
<Body className="text-xs-center m-b-3">
|
|
609
|
+
<code>showBottomBorder=false, done=false</code> → ❌ Border hidden
|
|
610
|
+
</Body>
|
|
611
|
+
</>
|
|
612
|
+
);
|
|
613
|
+
},
|
|
614
|
+
};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { Breakpoint } from '@transferwise/neptune-tokens';
|
|
2
|
+
import AvatarView from '../avatarView';
|
|
3
|
+
import { mockMatchMedia, render, screen } from '../test-utils';
|
|
4
|
+
|
|
5
|
+
import FlowNavigation, { type FlowNavigationProps } from './FlowNavigation';
|
|
6
|
+
|
|
7
|
+
mockMatchMedia();
|
|
8
|
+
|
|
9
|
+
jest.mock('./animatedLabel', () => {
|
|
10
|
+
return function ({ className, activeLabel }: { className: string; activeLabel: number }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={className} data-testid={`activeLabel-${activeLabel}`}>
|
|
13
|
+
AnimatedLabel
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('FlowNavigation', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
window.innerWidth = Breakpoint.LARGE + 1;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const props: FlowNavigationProps = {
|
|
25
|
+
avatar: <AvatarView profileName="Tim Mike" />,
|
|
26
|
+
logo: <img alt="logo" src="logo.svg" width="138" height="24" />,
|
|
27
|
+
onClose: jest.fn(),
|
|
28
|
+
steps: [
|
|
29
|
+
{
|
|
30
|
+
label: 'label-0',
|
|
31
|
+
},
|
|
32
|
+
{ label: 'label-1' },
|
|
33
|
+
{ label: 'label-2' },
|
|
34
|
+
],
|
|
35
|
+
activeStep: 0,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
it(`renders full Logo`, () => {
|
|
39
|
+
render(<FlowNavigation {...props} />);
|
|
40
|
+
expect(logoFull()).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it(`renders separator if avatar and onClose are provided`, () => {
|
|
44
|
+
const { container } = render(<FlowNavigation {...props} />);
|
|
45
|
+
|
|
46
|
+
expect(container.querySelector('span.m-x-1')).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it(`doesn't render separator if avatar or onClose are not provided`, () => {
|
|
50
|
+
const { container, rerender } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
51
|
+
|
|
52
|
+
expect(container.querySelector('.separator')).not.toBeInTheDocument();
|
|
53
|
+
|
|
54
|
+
rerender(<FlowNavigation {...props} avatar={null} />);
|
|
55
|
+
|
|
56
|
+
expect(container.querySelector('.separator')).not.toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`doesn't render separator if done is true`, () => {
|
|
60
|
+
const { container, rerender } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
61
|
+
|
|
62
|
+
expect(container.querySelector('.separator')).not.toBeInTheDocument();
|
|
63
|
+
|
|
64
|
+
rerender(<FlowNavigation {...props} avatar={null} done />);
|
|
65
|
+
|
|
66
|
+
expect(container.querySelector('.separator')).not.toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it(`renders border based on done`, () => {
|
|
70
|
+
const { container, rerender } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
71
|
+
|
|
72
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
73
|
+
|
|
74
|
+
rerender(<FlowNavigation {...props} avatar={null} done />);
|
|
75
|
+
|
|
76
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it(`hides stepper when done is true`, () => {
|
|
80
|
+
const { container, rerender } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
81
|
+
|
|
82
|
+
expect(container.querySelector('.np-flow-navigation__stepper')).toBeInTheDocument();
|
|
83
|
+
|
|
84
|
+
rerender(<FlowNavigation {...props} avatar={null} done />);
|
|
85
|
+
|
|
86
|
+
expect(container.querySelector('.np-flow-navigation__stepper')).not.toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it(`does not render stepper when steps is empty`, () => {
|
|
90
|
+
const { container } = render(<FlowNavigation {...props} steps={[]} />);
|
|
91
|
+
|
|
92
|
+
expect(container.querySelector('.np-flow-navigation__stepper')).not.toBeInTheDocument();
|
|
93
|
+
expect(container.querySelector('.tw-stepper')).not.toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it(`hides label when done is true`, () => {
|
|
97
|
+
const { rerender } = render(<FlowNavigation {...props} done={false} />);
|
|
98
|
+
|
|
99
|
+
expect(screen.getByText('label-0')).toBeInTheDocument();
|
|
100
|
+
|
|
101
|
+
rerender(<FlowNavigation {...props} done />);
|
|
102
|
+
|
|
103
|
+
expect(screen.queryByText('label-0')).not.toBeInTheDocument();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it(`renders xs-max class`, () => {
|
|
107
|
+
window.innerWidth = Breakpoint.SMALL - 1;
|
|
108
|
+
const { container } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
109
|
+
|
|
110
|
+
expect(container.querySelector('.np-flow-navigation--xs-max')).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it(`renders sm class`, () => {
|
|
114
|
+
window.innerWidth = Breakpoint.SMALL;
|
|
115
|
+
const { container } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
116
|
+
|
|
117
|
+
expect(container.querySelector('.np-flow-navigation--sm')).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it(`renders lg class`, () => {
|
|
121
|
+
window.innerWidth = Breakpoint.LARGE;
|
|
122
|
+
const { container } = render(<FlowNavigation {...props} onClose={undefined} />);
|
|
123
|
+
|
|
124
|
+
expect(container.querySelector('.np-flow-navigation--lg')).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('on mobile', () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
window.innerWidth = Breakpoint.SMALL - 1;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders Logo', () => {
|
|
133
|
+
render(<FlowNavigation {...props} />);
|
|
134
|
+
expect(screen.getByAltText('logo')).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('renders flag if activeStep <= 0 onGoBack or is not provided', () => {
|
|
138
|
+
const { rerender } = render(
|
|
139
|
+
<FlowNavigation {...props} activeStep={0} onGoBack={undefined} />,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const flag = screen.queryByAltText('logo');
|
|
143
|
+
|
|
144
|
+
expect(flag).toBeInTheDocument();
|
|
145
|
+
|
|
146
|
+
rerender(<FlowNavigation {...props} activeStep={1} onGoBack={undefined} />);
|
|
147
|
+
|
|
148
|
+
expect(flag).toBeInTheDocument();
|
|
149
|
+
|
|
150
|
+
rerender(<FlowNavigation {...props} activeStep={0} onGoBack={jest.fn()} />);
|
|
151
|
+
|
|
152
|
+
expect(flag).toBeInTheDocument();
|
|
153
|
+
|
|
154
|
+
rerender(<FlowNavigation {...props} activeStep={1} onGoBack={jest.fn()} />);
|
|
155
|
+
|
|
156
|
+
expect(flag).not.toBeInTheDocument();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('renders BackButton with AnimatedLabel if onGoBack is provided and activeStep > 0', () => {
|
|
160
|
+
const { rerender } = render(<FlowNavigation {...props} onGoBack={jest.fn()} />);
|
|
161
|
+
|
|
162
|
+
expect(
|
|
163
|
+
screen.queryByRole('button', { name: /back to previous step/i }),
|
|
164
|
+
).not.toBeInTheDocument();
|
|
165
|
+
|
|
166
|
+
rerender(<FlowNavigation {...props} activeStep={1} />);
|
|
167
|
+
expect(
|
|
168
|
+
screen.queryByRole('button', { name: /back to previous step/i }),
|
|
169
|
+
).not.toBeInTheDocument();
|
|
170
|
+
|
|
171
|
+
rerender(<FlowNavigation {...props} activeStep={1} onGoBack={jest.fn()} />);
|
|
172
|
+
|
|
173
|
+
expect(screen.getByRole('button', { name: /back to previous step/i })).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByText('AnimatedLabel')).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('renders correct AnimatedLabel', () => {
|
|
178
|
+
const { rerender } = render(
|
|
179
|
+
<FlowNavigation {...props} activeStep={1} onGoBack={jest.fn()} />,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(screen.getByTestId('activeLabel-1')).toBeInTheDocument();
|
|
183
|
+
|
|
184
|
+
rerender(<FlowNavigation {...props} activeStep={2} onGoBack={jest.fn()} />);
|
|
185
|
+
|
|
186
|
+
expect(screen.getByTestId('activeLabel-2')).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('composable variant', () => {
|
|
191
|
+
it('renders Container component when composable is true', () => {
|
|
192
|
+
const { container } = render(<FlowNavigation {...props} composable />);
|
|
193
|
+
|
|
194
|
+
expect(container.querySelector('.wds-container')).toBeInTheDocument();
|
|
195
|
+
expect(container.querySelector('.wds-container--fluid')).toBeInTheDocument();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('renders Container as root element with FlowNavigation classes when composable is true', () => {
|
|
199
|
+
const { container } = render(<FlowNavigation {...props} composable />);
|
|
200
|
+
|
|
201
|
+
const containerElement = container.querySelector('.wds-container');
|
|
202
|
+
expect(containerElement).toHaveClass('np-flow-navigation');
|
|
203
|
+
expect(containerElement).toHaveClass('np-flow-navigation--composable');
|
|
204
|
+
expect(containerElement).toHaveClass('np-flow-navigation--border-bottom');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('does not apply layout-specific classes to composable variant', () => {
|
|
208
|
+
const { container } = render(<FlowNavigation {...props} composable />);
|
|
209
|
+
|
|
210
|
+
const containerElement = container.querySelector('.wds-container');
|
|
211
|
+
// These layout classes should NOT be present on composable variant
|
|
212
|
+
expect(containerElement).not.toHaveClass('d-flex');
|
|
213
|
+
expect(containerElement).not.toHaveClass('align-items-center');
|
|
214
|
+
expect(containerElement).not.toHaveClass('justify-content-center');
|
|
215
|
+
expect(containerElement).not.toHaveClass('p-y-3');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('renders div as root element when composable is false', () => {
|
|
219
|
+
const { container } = render(<FlowNavigation {...props} composable={false} />);
|
|
220
|
+
|
|
221
|
+
const flowNavElement = container.querySelector('.np-flow-navigation');
|
|
222
|
+
expect(flowNavElement?.tagName).toBe('DIV');
|
|
223
|
+
expect(flowNavElement).not.toHaveClass('wds-container');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('does not render Container component when composable is false', () => {
|
|
227
|
+
const { container } = render(<FlowNavigation {...props} composable={false} />);
|
|
228
|
+
|
|
229
|
+
expect(container.querySelector('.wds-container')).not.toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('does not render Container component by default', () => {
|
|
233
|
+
const { container } = render(<FlowNavigation {...props} />);
|
|
234
|
+
|
|
235
|
+
expect(container.querySelector('.wds-container')).not.toBeInTheDocument();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('applies composable class when composable is true', () => {
|
|
239
|
+
const { container } = render(<FlowNavigation {...props} composable />);
|
|
240
|
+
|
|
241
|
+
expect(container.querySelector('.np-flow-navigation--composable')).toBeInTheDocument();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('does not apply composable class when composable is false', () => {
|
|
245
|
+
const { container } = render(<FlowNavigation {...props} composable={false} />);
|
|
246
|
+
|
|
247
|
+
expect(container.querySelector('.np-flow-navigation--composable')).not.toBeInTheDocument();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('does not apply horizontal padding to FlowHeader when composable', () => {
|
|
251
|
+
const { container } = render(<FlowNavigation {...props} composable />);
|
|
252
|
+
|
|
253
|
+
const flowHeaderContent = container.querySelector('.np-flow-navigation__content');
|
|
254
|
+
expect(flowHeaderContent).not.toHaveClass('p-x-3');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('applies horizontal padding to FlowHeader when not composable', () => {
|
|
258
|
+
const { container } = render(<FlowNavigation {...props} composable={false} />);
|
|
259
|
+
|
|
260
|
+
const flowHeaderContent = container.querySelector('.np-flow-navigation__content');
|
|
261
|
+
expect(flowHeaderContent).toHaveClass('p-x-3');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('applies horizontal padding to FlowHeader by default', () => {
|
|
265
|
+
const { container } = render(<FlowNavigation {...props} />);
|
|
266
|
+
|
|
267
|
+
const flowHeaderContent = container.querySelector('.np-flow-navigation__content');
|
|
268
|
+
expect(flowHeaderContent).toHaveClass('p-x-3');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('showBottomBorder prop', () => {
|
|
273
|
+
it('shows border by default when done=false', () => {
|
|
274
|
+
const { container } = render(<FlowNavigation {...props} done={false} />);
|
|
275
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('hides border by default when done=true', () => {
|
|
279
|
+
const { container } = render(<FlowNavigation {...props} done />);
|
|
280
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('shows border when showBottomBorder=true and done=false', () => {
|
|
284
|
+
const { container } = render(<FlowNavigation {...props} showBottomBorder done={false} />);
|
|
285
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('hides border when showBottomBorder=true and done=true', () => {
|
|
289
|
+
const { container } = render(<FlowNavigation {...props} showBottomBorder done />);
|
|
290
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('always hides border when showBottomBorder=false regardless of done', () => {
|
|
294
|
+
const { container, rerender } = render(
|
|
295
|
+
<FlowNavigation {...props} done={false} showBottomBorder={false} />,
|
|
296
|
+
);
|
|
297
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
298
|
+
|
|
299
|
+
rerender(<FlowNavigation {...props} done showBottomBorder={false} />);
|
|
300
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('follows done prop when showBottomBorder=true (default)', () => {
|
|
304
|
+
const { container, rerender } = render(<FlowNavigation {...props} done={false} />);
|
|
305
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
306
|
+
|
|
307
|
+
rerender(<FlowNavigation {...props} done />);
|
|
308
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('composable variant shows border when showBottomBorder=true regardless of done', () => {
|
|
312
|
+
const { container, rerender } = render(
|
|
313
|
+
<FlowNavigation {...props} composable showBottomBorder done={false} />,
|
|
314
|
+
);
|
|
315
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
316
|
+
|
|
317
|
+
rerender(<FlowNavigation {...props} composable showBottomBorder done />);
|
|
318
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).toBeInTheDocument();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('composable variant hides border when showBottomBorder=false regardless of done', () => {
|
|
322
|
+
const { container, rerender } = render(
|
|
323
|
+
<FlowNavigation {...props} composable showBottomBorder={false} done={false} />,
|
|
324
|
+
);
|
|
325
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
326
|
+
|
|
327
|
+
rerender(<FlowNavigation {...props} composable showBottomBorder={false} done />);
|
|
328
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('can completely disable bottom border with showBottomBorder=false', () => {
|
|
332
|
+
const { container } = render(<FlowNavigation {...props} showBottomBorder={false} />);
|
|
333
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('disabled bottom border works with composable variant', () => {
|
|
337
|
+
const { container } = render(
|
|
338
|
+
<FlowNavigation {...props} composable showBottomBorder={false} />,
|
|
339
|
+
);
|
|
340
|
+
expect(container.querySelector('.np-flow-navigation--border-bottom')).not.toBeInTheDocument();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const logoFull = () => screen.getByAltText(`logo`);
|
|
345
|
+
});
|