@simplybusiness/mobius 6.3.2 → 6.3.3
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/CHANGELOG.md +7 -0
- package/dist/cjs/index.js +11 -4
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.js +12 -5
- package/dist/types/src/components/AddressLookup/types.d.ts +3 -0
- package/dist/types/src/components/ExpandableText/ExpandableText.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AddressLookup/types.tsx +4 -0
- package/src/components/ExpandableText/ExpandableText.test.tsx +10 -4
- package/src/components/ExpandableText/ExpandableText.tsx +5 -3
- package/src/components/MaskedField/MaskedField.test.tsx +145 -0
- package/src/components/MaskedField/MaskedField.tsx +11 -2
package/dist/esm/index.js
CHANGED
|
@@ -4423,7 +4423,7 @@ var ExpandableText = forwardRef55((props, ref) => {
|
|
|
4423
4423
|
setIsCollapsed(isOverflowing);
|
|
4424
4424
|
}, [text, shouldCollapse, maxLines]);
|
|
4425
4425
|
if (breakpoint && !shouldCollapse) {
|
|
4426
|
-
return /* @__PURE__ */ jsx70("div", { ref, className, ...otherProps, children: /* @__PURE__ */ jsx70(Text, { ...textProps, children: text }) });
|
|
4426
|
+
return /* @__PURE__ */ jsx70("div", { ref, className, ...otherProps, children: /* @__PURE__ */ jsx70(Text, { ...textProps, children: /* @__PURE__ */ jsx70("span", { dangerouslySetInnerHTML: { __html: text } }) }) });
|
|
4427
4427
|
}
|
|
4428
4428
|
const handleAccordionChange = (expanded) => {
|
|
4429
4429
|
setIsExpanded(expanded);
|
|
@@ -4452,7 +4452,7 @@ var ExpandableText = forwardRef55((props, ref) => {
|
|
|
4452
4452
|
style: textContainerStyle,
|
|
4453
4453
|
"data-testid": "expandable-text-content",
|
|
4454
4454
|
"aria-describedby": isCollapsed ? expandButtonId : void 0,
|
|
4455
|
-
children: /* @__PURE__ */ jsx70(Text, { elementType: "span", ...textProps, children: text })
|
|
4455
|
+
children: /* @__PURE__ */ jsx70(Text, { elementType: "span", ...textProps, children: /* @__PURE__ */ jsx70("span", { dangerouslySetInnerHTML: { __html: text } }) })
|
|
4456
4456
|
}
|
|
4457
4457
|
),
|
|
4458
4458
|
isCollapsed && /* @__PURE__ */ jsx70(
|
|
@@ -4476,7 +4476,7 @@ var ExpandableText = forwardRef55((props, ref) => {
|
|
|
4476
4476
|
ExpandableText.displayName = "ExpandableText";
|
|
4477
4477
|
|
|
4478
4478
|
// src/components/MaskedField/MaskedField.tsx
|
|
4479
|
-
import { forwardRef as forwardRef56 } from "react";
|
|
4479
|
+
import { forwardRef as forwardRef56, useEffect as useEffect26 } from "react";
|
|
4480
4480
|
import { useIMask } from "react-imask";
|
|
4481
4481
|
import { jsx as jsx71 } from "react/jsx-runtime";
|
|
4482
4482
|
var createSyntheticEvent = (options) => {
|
|
@@ -4511,10 +4511,17 @@ var MaskedField = forwardRef56((props, ref) => {
|
|
|
4511
4511
|
const {
|
|
4512
4512
|
ref: maskRef,
|
|
4513
4513
|
value: maskedValue,
|
|
4514
|
-
unmaskedValue
|
|
4514
|
+
unmaskedValue,
|
|
4515
|
+
setValue
|
|
4515
4516
|
} = useIMask(mask, {
|
|
4516
|
-
defaultValue
|
|
4517
|
+
defaultValue: value || defaultValue
|
|
4517
4518
|
});
|
|
4519
|
+
useEffect26(() => {
|
|
4520
|
+
const valueToCompare = useMaskedValue ? maskedValue : unmaskedValue;
|
|
4521
|
+
if (value !== void 0 && value !== valueToCompare) {
|
|
4522
|
+
setValue(value);
|
|
4523
|
+
}
|
|
4524
|
+
}, [value, setValue]);
|
|
4518
4525
|
const handleChange = (event) => {
|
|
4519
4526
|
if (onChange) {
|
|
4520
4527
|
onChange(
|
|
@@ -33,6 +33,9 @@ export type LoqateAddressDetailsItem = {
|
|
|
33
33
|
Line2: string;
|
|
34
34
|
PostalCode: string;
|
|
35
35
|
Label: string;
|
|
36
|
+
SubBuilding?: string;
|
|
37
|
+
BuildingNumber?: string;
|
|
38
|
+
Street?: string;
|
|
36
39
|
};
|
|
37
40
|
export type LoqateAddressDetailsResponse = {
|
|
38
41
|
Items: LoqateAddressDetailsItem[];
|
|
@@ -5,7 +5,7 @@ import type { AccordionProps } from "../Accordion/Accordion";
|
|
|
5
5
|
export type ExpandableTextElementType = HTMLDivElement;
|
|
6
6
|
export type ExpandableTextRef = React.Ref<ExpandableTextElementType>;
|
|
7
7
|
export interface ExpandableTextProps extends DOMProps {
|
|
8
|
-
/** The text content to display */
|
|
8
|
+
/** The text content to display (can be plain text or HTML) */
|
|
9
9
|
text: string;
|
|
10
10
|
/** Maximum number of lines to show when collapsed */
|
|
11
11
|
maxLines?: number;
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { render, screen, within } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { ExpandableText } from "./ExpandableText";
|
|
4
4
|
|
|
@@ -184,9 +184,15 @@ describe("ExpandableText", () => {
|
|
|
184
184
|
/>,
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
// Verify the text is rendered within an element that has all the custom classes
|
|
188
|
+
const content = screen.getByTestId("expandable-text-content");
|
|
189
|
+
|
|
190
|
+
// The Text component with custom classes should be within the content
|
|
191
|
+
const textWithCustomClass = within(content).getByText(longText, {
|
|
192
|
+
selector: ".mobius-text.custom-text-class.--is-small *",
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(textWithCustomClass).toBeInTheDocument();
|
|
190
196
|
});
|
|
191
197
|
|
|
192
198
|
it("passes accordionProps to Accordion component", async () => {
|
|
@@ -12,7 +12,7 @@ export type ExpandableTextElementType = HTMLDivElement;
|
|
|
12
12
|
export type ExpandableTextRef = React.Ref<ExpandableTextElementType>;
|
|
13
13
|
|
|
14
14
|
export interface ExpandableTextProps extends DOMProps {
|
|
15
|
-
/** The text content to display */
|
|
15
|
+
/** The text content to display (can be plain text or HTML) */
|
|
16
16
|
text: string;
|
|
17
17
|
/** Maximum number of lines to show when collapsed */
|
|
18
18
|
maxLines?: number;
|
|
@@ -77,7 +77,9 @@ export const ExpandableText: ForwardedRefComponent<
|
|
|
77
77
|
if (breakpoint && !shouldCollapse) {
|
|
78
78
|
return (
|
|
79
79
|
<div ref={ref} className={className} {...otherProps}>
|
|
80
|
-
<Text {...textProps}>
|
|
80
|
+
<Text {...textProps}>
|
|
81
|
+
<span dangerouslySetInnerHTML={{ __html: text }} />
|
|
82
|
+
</Text>
|
|
81
83
|
</div>
|
|
82
84
|
);
|
|
83
85
|
}
|
|
@@ -112,7 +114,7 @@ export const ExpandableText: ForwardedRefComponent<
|
|
|
112
114
|
aria-describedby={isCollapsed ? expandButtonId : undefined}
|
|
113
115
|
>
|
|
114
116
|
<Text elementType="span" {...textProps}>
|
|
115
|
-
{text}
|
|
117
|
+
<span dangerouslySetInnerHTML={{ __html: text }} />
|
|
116
118
|
</Text>
|
|
117
119
|
</div>
|
|
118
120
|
{isCollapsed && (
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { render, screen } from "@testing-library/react";
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import { MaskedField } from ".";
|
|
5
|
+
import type { ReactMaskOpts } from "react-imask";
|
|
5
6
|
|
|
6
7
|
const commaSeparatedNumberMask = {
|
|
7
8
|
mask: Number,
|
|
@@ -17,6 +18,30 @@ const usPhoneNumberMask = {
|
|
|
17
18
|
const WRAPPER_CLASS_NAME = "mobius-text-field";
|
|
18
19
|
const INPUT_CLASS_NAME = "mobius-text-field__input";
|
|
19
20
|
|
|
21
|
+
// Helper component for controlled component tests
|
|
22
|
+
const TestComponent = ({
|
|
23
|
+
value,
|
|
24
|
+
mask = usPhoneNumberMask,
|
|
25
|
+
useMaskedValue,
|
|
26
|
+
label = "Phone Number",
|
|
27
|
+
...props
|
|
28
|
+
}: {
|
|
29
|
+
value: string;
|
|
30
|
+
mask?: ReactMaskOpts;
|
|
31
|
+
useMaskedValue?: boolean;
|
|
32
|
+
label?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}) => (
|
|
35
|
+
<MaskedField
|
|
36
|
+
mask={mask}
|
|
37
|
+
label={label}
|
|
38
|
+
data-testid="masked-field"
|
|
39
|
+
value={value}
|
|
40
|
+
useMaskedValue={useMaskedValue}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
|
|
20
45
|
describe("MaskedField", () => {
|
|
21
46
|
it("should render without errors", () => {
|
|
22
47
|
render(
|
|
@@ -390,6 +415,126 @@ describe("MaskedField", () => {
|
|
|
390
415
|
const input = screen.getByTestId("masked-field");
|
|
391
416
|
expect(input).toHaveValue("(123) 456-7890");
|
|
392
417
|
});
|
|
418
|
+
|
|
419
|
+
it("should update when value prop changes (controlled behavior)", () => {
|
|
420
|
+
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
421
|
+
|
|
422
|
+
const input = screen.getByTestId("masked-field");
|
|
423
|
+
expect(input).toHaveValue("(123) 456-7890");
|
|
424
|
+
|
|
425
|
+
// Change the value prop to simulate server-side update
|
|
426
|
+
rerender(<TestComponent value="9876543210" />);
|
|
427
|
+
|
|
428
|
+
// The input should reflect the new value
|
|
429
|
+
expect(input).toHaveValue("(987) 654-3210");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("should handle masked value input when useMaskedValue is true", () => {
|
|
433
|
+
const { rerender } = render(
|
|
434
|
+
<TestComponent value="(123) 456-7890" useMaskedValue={true} />,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const input = screen.getByTestId("masked-field");
|
|
438
|
+
expect(input).toHaveValue("(123) 456-7890");
|
|
439
|
+
|
|
440
|
+
// Change to a different masked value
|
|
441
|
+
rerender(<TestComponent value="(987) 654-3210" />);
|
|
442
|
+
|
|
443
|
+
// The input should reflect the new masked value
|
|
444
|
+
expect(input).toHaveValue("(987) 654-3210");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should handle unmasked value input when useMaskedValue is false", () => {
|
|
448
|
+
const { rerender } = render(
|
|
449
|
+
<TestComponent value="1234567890" useMaskedValue={false} />,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const input = screen.getByTestId("masked-field");
|
|
453
|
+
expect(input).toHaveValue("(123) 456-7890");
|
|
454
|
+
|
|
455
|
+
// Change to a different unmasked value
|
|
456
|
+
rerender(<TestComponent value="9876543210" />);
|
|
457
|
+
|
|
458
|
+
// The input should reflect the new value (displayed as masked)
|
|
459
|
+
expect(input).toHaveValue("(987) 654-3210");
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("should not update when value prop matches current internal state", () => {
|
|
463
|
+
const setValue = jest.fn();
|
|
464
|
+
|
|
465
|
+
// Mock useIMask to spy on setValue calls
|
|
466
|
+
const originalUseIMask = jest.requireActual("react-imask").useIMask;
|
|
467
|
+
const reactIMask = jest.requireActual("react-imask");
|
|
468
|
+
jest.spyOn(reactIMask, "useIMask").mockImplementation((mask, options) => {
|
|
469
|
+
const result = originalUseIMask(mask, options);
|
|
470
|
+
return {
|
|
471
|
+
...result,
|
|
472
|
+
setValue: setValue,
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
477
|
+
|
|
478
|
+
// Clear any initial setValue calls
|
|
479
|
+
setValue.mockClear();
|
|
480
|
+
|
|
481
|
+
// Re-render with the same value
|
|
482
|
+
rerender(<TestComponent value="1234567890" />);
|
|
483
|
+
|
|
484
|
+
// setValue should not be called since the value hasn't actually changed
|
|
485
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
486
|
+
|
|
487
|
+
// Restore original implementation
|
|
488
|
+
jest.restoreAllMocks();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should handle partial phone number updates", () => {
|
|
492
|
+
const { rerender } = render(<TestComponent value="123" />);
|
|
493
|
+
|
|
494
|
+
const input = screen.getByTestId("masked-field");
|
|
495
|
+
expect(input).toHaveValue("(123");
|
|
496
|
+
|
|
497
|
+
// Update to a longer partial number
|
|
498
|
+
rerender(<TestComponent value="12345" />);
|
|
499
|
+
|
|
500
|
+
expect(input).toHaveValue("(123) 45");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should handle empty value updates", () => {
|
|
504
|
+
const { rerender } = render(<TestComponent value="1234567890" />);
|
|
505
|
+
|
|
506
|
+
const input = screen.getByTestId("masked-field");
|
|
507
|
+
expect(input).toHaveValue("(123) 456-7890");
|
|
508
|
+
|
|
509
|
+
// Clear the value
|
|
510
|
+
rerender(<TestComponent value="" />);
|
|
511
|
+
|
|
512
|
+
expect(input).toHaveValue("");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("should work with number masks and controlled values", () => {
|
|
516
|
+
const { rerender } = render(
|
|
517
|
+
<TestComponent
|
|
518
|
+
value="1234567"
|
|
519
|
+
mask={commaSeparatedNumberMask}
|
|
520
|
+
label="Number"
|
|
521
|
+
/>,
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
const input = screen.getByTestId("masked-field");
|
|
525
|
+
expect(input).toHaveValue("1,234,567");
|
|
526
|
+
|
|
527
|
+
// Update to different number
|
|
528
|
+
rerender(
|
|
529
|
+
<TestComponent
|
|
530
|
+
value="9876543"
|
|
531
|
+
mask={commaSeparatedNumberMask}
|
|
532
|
+
label="Number"
|
|
533
|
+
/>,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
expect(input).toHaveValue("9,876,543");
|
|
537
|
+
});
|
|
393
538
|
});
|
|
394
539
|
|
|
395
540
|
describe("accessibility", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { Ref, RefAttributes } from "react";
|
|
4
|
-
import { forwardRef } from "react";
|
|
4
|
+
import { forwardRef, useEffect } from "react";
|
|
5
5
|
import type { ReactMaskOpts } from "react-imask";
|
|
6
6
|
import { useIMask } from "react-imask";
|
|
7
7
|
import type { DOMProps } from "../../types/dom";
|
|
@@ -72,10 +72,19 @@ export const MaskedField: ForwardedRefComponent<
|
|
|
72
72
|
ref: maskRef,
|
|
73
73
|
value: maskedValue,
|
|
74
74
|
unmaskedValue,
|
|
75
|
+
setValue,
|
|
75
76
|
} = useIMask(mask, {
|
|
76
|
-
defaultValue,
|
|
77
|
+
defaultValue: value || defaultValue,
|
|
77
78
|
});
|
|
78
79
|
|
|
80
|
+
// Handle controlled behavior - sync external value changes with internal mask state
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const valueToCompare = useMaskedValue ? maskedValue : unmaskedValue;
|
|
83
|
+
if (value !== undefined && value !== valueToCompare) {
|
|
84
|
+
setValue(value);
|
|
85
|
+
}
|
|
86
|
+
}, [value, setValue]);
|
|
87
|
+
|
|
79
88
|
// Enhanced onChange handler that provides the unmasked value
|
|
80
89
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
81
90
|
if (onChange) {
|