@abstraks-dev/ui-library 1.1.26 → 1.1.29

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.
Files changed (109) hide show
  1. package/dist/__tests__/AccountBox.test.js +61 -0
  2. package/dist/__tests__/AccountCircle.test.js +69 -0
  3. package/dist/__tests__/Alert.test.js +144 -0
  4. package/dist/__tests__/ArrowIcon.test.js +61 -0
  5. package/dist/__tests__/ArrowRight.test.js +12 -7
  6. package/dist/__tests__/BookOpen.test.js +61 -0
  7. package/dist/__tests__/Camera.test.js +69 -0
  8. package/dist/__tests__/CaretDown.test.js +69 -0
  9. package/dist/__tests__/ChevronDown.test.js +13 -8
  10. package/dist/__tests__/Comment.test.js +69 -0
  11. package/dist/__tests__/Ellipses.test.js +61 -0
  12. package/dist/__tests__/Explore.test.js +61 -0
  13. package/dist/__tests__/FileInput.test.js +148 -0
  14. package/dist/__tests__/Filter.test.js +69 -0
  15. package/dist/__tests__/Form.test.js +471 -0
  16. package/dist/__tests__/Group.test.js +61 -0
  17. package/dist/__tests__/GroupReview.test.js +61 -0
  18. package/dist/__tests__/Hamburger.test.js +69 -0
  19. package/dist/__tests__/Header.test.js +211 -0
  20. package/dist/__tests__/Heart.test.js +69 -0
  21. package/dist/__tests__/Home.test.js +69 -0
  22. package/dist/__tests__/LoadingSpinner.test.js +78 -0
  23. package/dist/__tests__/LogOut.test.js +69 -0
  24. package/dist/__tests__/Magnify.test.js +69 -0
  25. package/dist/__tests__/News.test.js +61 -0
  26. package/dist/__tests__/Review.test.js +61 -0
  27. package/dist/__tests__/SaveIcon.test.js +61 -0
  28. package/dist/__tests__/Search.test.js +101 -0
  29. package/dist/__tests__/utils/accessibility.test.js +361 -0
  30. package/dist/__tests__/utils/inputValidation-core.test.js +80 -0
  31. package/dist/__tests__/utils/validation-core.test.js +123 -0
  32. package/dist/__tests__/utils/validation.test.js +362 -0
  33. package/dist/components/Alert.js +104 -0
  34. package/dist/components/FileInput.js +96 -0
  35. package/dist/components/Form.js +27 -3
  36. package/dist/components/Header.js +1 -1
  37. package/dist/components/Search.js +238 -0
  38. package/dist/icons/AccountBox.js +33 -0
  39. package/dist/icons/AccountCircle.js +33 -0
  40. package/dist/icons/ArrowIcon.js +3 -2
  41. package/dist/icons/ArrowRight.js +23 -12
  42. package/dist/icons/BookOpen.js +33 -0
  43. package/dist/icons/Camera.js +33 -0
  44. package/dist/icons/CaretDown.js +33 -0
  45. package/dist/icons/ChevronDown.js +19 -9
  46. package/dist/icons/Comment.js +33 -0
  47. package/dist/icons/Explore.js +33 -0
  48. package/dist/icons/Filter.js +33 -0
  49. package/dist/icons/Group.js +33 -0
  50. package/dist/icons/GroupReview.js +33 -0
  51. package/dist/icons/Hamburger.js +14 -20
  52. package/dist/icons/Heart.js +33 -0
  53. package/dist/icons/Home.js +33 -0
  54. package/dist/icons/LoadingSpinner.js +3 -2
  55. package/dist/icons/LogOut.js +33 -0
  56. package/dist/icons/Magnify.js +33 -0
  57. package/dist/icons/News.js +33 -0
  58. package/dist/icons/Review.js +35 -0
  59. package/dist/icons/SaveIcon.js +3 -2
  60. package/dist/icons/index.js +112 -0
  61. package/dist/index.js +41 -1
  62. package/dist/styles/_variables.scss +5 -0
  63. package/dist/styles/alert.css +218 -0
  64. package/dist/styles/alert.css.map +1 -0
  65. package/dist/styles/alert.scss +128 -0
  66. package/dist/styles/anchor.css.map +1 -1
  67. package/dist/styles/avatar.css.map +1 -1
  68. package/dist/styles/button.css.map +1 -1
  69. package/dist/styles/card.css.map +1 -1
  70. package/dist/styles/checkbox.css.map +1 -1
  71. package/dist/styles/crud.css.map +1 -1
  72. package/dist/styles/dragAndDrop.css.map +1 -1
  73. package/dist/styles/error.css.map +1 -1
  74. package/dist/styles/file-input.css +165 -0
  75. package/dist/styles/file-input.css.map +1 -0
  76. package/dist/styles/file-input.scss +69 -0
  77. package/dist/styles/footer.css.map +1 -1
  78. package/dist/styles/form.css.map +1 -1
  79. package/dist/styles/header.css.map +1 -1
  80. package/dist/styles/heading.css.map +1 -1
  81. package/dist/styles/hero.css.map +1 -1
  82. package/dist/styles/htmlElements.css.map +1 -1
  83. package/dist/styles/label.css.map +1 -1
  84. package/dist/styles/loader.css.map +1 -1
  85. package/dist/styles/main.css +344 -11
  86. package/dist/styles/main.css.map +1 -1
  87. package/dist/styles/menu-hover.css.map +1 -1
  88. package/dist/styles/paragraph.css.map +1 -1
  89. package/dist/styles/prompt.css.map +1 -1
  90. package/dist/styles/radio.css.map +1 -1
  91. package/dist/styles/search.css +302 -0
  92. package/dist/styles/search.css.map +1 -0
  93. package/dist/styles/search.scss +260 -0
  94. package/dist/styles/select.css.map +1 -1
  95. package/dist/styles/side-menu.css.map +1 -1
  96. package/dist/styles/tabs.css.map +1 -1
  97. package/dist/styles/text-area.css.map +1 -1
  98. package/dist/styles/text-input.css +0 -11
  99. package/dist/styles/text-input.css.map +1 -1
  100. package/dist/styles/text-input.scss +0 -14
  101. package/dist/utils/utils/validation.js +2 -2
  102. package/dist/utils/validation.js +2 -2
  103. package/package.json +1 -1
  104. package/dist/icons/__tests__/CheckCircle.test.js +0 -9
  105. package/dist/icons/__tests__/ChevronDown.test.js +0 -9
  106. package/dist/icons/__tests__/Close.test.js +0 -9
  107. package/dist/icons/__tests__/EditSquare.test.js +0 -9
  108. package/dist/icons/__tests__/PlusCircle.test.js +0 -9
  109. package/dist/icons/__tests__/TrashX.test.js +0 -9
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ var _react = _interopRequireDefault(require("react"));
4
+ var _react2 = require("@testing-library/react");
5
+ var _Review = require("../icons/Review");
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ describe('Review Icon', () => {
8
+ test('renders with default props', () => {
9
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, null));
10
+ const svg = document.querySelector('svg');
11
+ expect(svg).toBeInTheDocument();
12
+ expect(svg.tagName).toBe('svg');
13
+ });
14
+ test('applies correct default dimensions', () => {
15
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, null));
16
+ const svg = document.querySelector('svg');
17
+ expect(svg).toHaveAttribute('width', '24');
18
+ expect(svg).toHaveAttribute('height', '24');
19
+ });
20
+ test('applies custom dimensions', () => {
21
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, {
22
+ dimensions: 32
23
+ }));
24
+ const svg = document.querySelector('svg');
25
+ expect(svg).toHaveAttribute('width', '32');
26
+ expect(svg).toHaveAttribute('height', '32');
27
+ });
28
+ test('applies correct CSS classes', () => {
29
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, null));
30
+ const svg = document.querySelector('svg');
31
+ expect(svg).toHaveClass('icon', 'review');
32
+ });
33
+ test('applies additional className', () => {
34
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, {
35
+ additionalClassName: "custom-class"
36
+ }));
37
+ const svg = document.querySelector('svg');
38
+ expect(svg).toHaveClass('icon', 'review', 'custom-class');
39
+ });
40
+ test('applies custom fill color', () => {
41
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, {
42
+ fill: "#ff0000"
43
+ }));
44
+ const svg = document.querySelector('svg');
45
+ expect(svg).toHaveAttribute('fill', '#ff0000');
46
+ });
47
+ test('contains correct path data', () => {
48
+ const {
49
+ container
50
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, null));
51
+ const path = container.querySelector('path');
52
+ expect(path).toHaveAttribute('d', 'M240-400h122l200-200q9-9 13.5-20.5T580-643q0-11-5-21.5T562-684l-36-38q-9-9-20-13.5t-23-4.5q-11 0-22.5 4.5T440-722L240-522v122Zm280-243-37-37 37 37ZM300-460v-38l101-101 20 18 18 20-101 101h-38Zm121-121 18 20-38-38 20 18Zm26 181h273v-80H527l-80 80ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z');
53
+ });
54
+ test('contains single path element', () => {
55
+ const {
56
+ container
57
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Review.Review, null));
58
+ const paths = container.querySelectorAll('path');
59
+ expect(paths).toHaveLength(1);
60
+ });
61
+ });
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ var _react = _interopRequireDefault(require("react"));
4
+ var _react2 = require("@testing-library/react");
5
+ var _SaveIcon = require("../icons/SaveIcon");
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ describe('SaveIcon Icon', () => {
8
+ test('renders with default props', () => {
9
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, null));
10
+ const svg = document.querySelector('svg');
11
+ expect(svg).toBeInTheDocument();
12
+ expect(svg.tagName).toBe('svg');
13
+ });
14
+ test('applies correct default dimensions', () => {
15
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, null));
16
+ const svg = document.querySelector('svg');
17
+ expect(svg).toHaveAttribute('width', '24');
18
+ expect(svg).toHaveAttribute('height', '24');
19
+ });
20
+ test('applies custom dimensions', () => {
21
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, {
22
+ dimensions: 32
23
+ }));
24
+ const svg = document.querySelector('svg');
25
+ expect(svg).toHaveAttribute('width', '32');
26
+ expect(svg).toHaveAttribute('height', '32');
27
+ });
28
+ test('applies correct CSS classes', () => {
29
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, null));
30
+ const svg = document.querySelector('svg');
31
+ expect(svg).toHaveClass('icon', 'save-icon');
32
+ });
33
+ test('applies additional className', () => {
34
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, {
35
+ additionalClassName: "custom-class"
36
+ }));
37
+ const svg = document.querySelector('svg');
38
+ expect(svg).toHaveClass('icon', 'save-icon', 'custom-class');
39
+ });
40
+ test('applies custom fill color', () => {
41
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, {
42
+ fill: "#ff0000"
43
+ }));
44
+ const svg = document.querySelector('svg');
45
+ expect(svg).toHaveAttribute('fill', '#ff0000');
46
+ });
47
+ test('contains correct path data', () => {
48
+ const {
49
+ container
50
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, null));
51
+ const path = container.querySelector('path');
52
+ expect(path).toHaveAttribute('d', 'M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2z');
53
+ });
54
+ test('contains single path element', () => {
55
+ const {
56
+ container
57
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_SaveIcon.SaveIcon, null));
58
+ const paths = container.querySelectorAll('path');
59
+ expect(paths).toHaveLength(1);
60
+ });
61
+ });
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+
3
+ var _react = _interopRequireDefault(require("react"));
4
+ var _react2 = require("@testing-library/react");
5
+ var _Search = require("../components/Search");
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ describe('Search Component', () => {
8
+ const mockOnSearchChange = jest.fn();
9
+ const sampleData = [{
10
+ id: 1,
11
+ name: 'Jane Smith',
12
+ email: 'jane@example.com'
13
+ }, {
14
+ id: 2,
15
+ name: 'Bob Johnson',
16
+ email: 'bob@example.com'
17
+ }];
18
+ beforeEach(() => {
19
+ jest.clearAllMocks();
20
+ });
21
+ test('does not show results when search value is empty', () => {
22
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Search.Search, {
23
+ placeholder: "Search...",
24
+ searchValue: "",
25
+ onSearchChange: mockOnSearchChange,
26
+ data: sampleData,
27
+ searchFields: ['name'],
28
+ renderResult: ({
29
+ item
30
+ }) => /*#__PURE__*/_react.default.createElement("div", {
31
+ "data-testid": "search-result"
32
+ }, item.name)
33
+ }));
34
+ const results = _react2.screen.queryAllByTestId('search-result');
35
+ expect(results).toHaveLength(0);
36
+ });
37
+ test('shows results when typing', () => {
38
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Search.Search, {
39
+ placeholder: "Search...",
40
+ searchValue: "Jane",
41
+ onSearchChange: mockOnSearchChange,
42
+ data: sampleData,
43
+ searchFields: ['name'],
44
+ renderResult: ({
45
+ item
46
+ }) => /*#__PURE__*/_react.default.createElement("div", {
47
+ "data-testid": "search-result"
48
+ }, item.name)
49
+ }));
50
+ const results = _react2.screen.getAllByTestId('search-result');
51
+ expect(results).toHaveLength(1);
52
+ expect(results[0]).toHaveTextContent('Jane Smith');
53
+ });
54
+ test('renders with magnifying glass icon by default', () => {
55
+ const {
56
+ container
57
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Search.Search, {
58
+ placeholder: "Search...",
59
+ searchValue: "",
60
+ onSearchChange: mockOnSearchChange,
61
+ data: sampleData,
62
+ searchFields: ['name']
63
+ }));
64
+ const iconContainer = container.querySelector('.search-icon');
65
+ expect(iconContainer).toBeInTheDocument();
66
+ const svgIcon = container.querySelector('.search-magnify-icon');
67
+ expect(svgIcon).toBeInTheDocument();
68
+ });
69
+ test('hides icon when showIcon is false', () => {
70
+ const {
71
+ container
72
+ } = (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Search.Search, {
73
+ placeholder: "Search...",
74
+ searchValue: "",
75
+ onSearchChange: mockOnSearchChange,
76
+ data: sampleData,
77
+ searchFields: ['name'],
78
+ showIcon: false
79
+ }));
80
+ const iconContainer = container.querySelector('.search-icon');
81
+ expect(iconContainer).not.toBeInTheDocument();
82
+ });
83
+ test('renders search button when showButton is true', () => {
84
+ const mockButtonClick = jest.fn();
85
+ (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_Search.Search, {
86
+ placeholder: "Search...",
87
+ searchValue: "",
88
+ onSearchChange: mockOnSearchChange,
89
+ data: sampleData,
90
+ searchFields: ['name'],
91
+ showButton: true,
92
+ buttonText: "Search",
93
+ onButtonClick: mockButtonClick
94
+ }));
95
+ const button = _react2.screen.getByRole('button', {
96
+ name: /search/i
97
+ });
98
+ expect(button).toBeInTheDocument();
99
+ expect(button).toHaveTextContent('Search');
100
+ });
101
+ });
@@ -0,0 +1,361 @@
1
+ "use strict";
2
+
3
+ var _accessibility = require("../../utils/accessibility.js");
4
+ /**
5
+ * @jest-environment jsdom
6
+ */
7
+
8
+ describe('Accessibility Utilities', () => {
9
+ // Clean up any leftover DOM elements after each test
10
+ afterEach(() => {
11
+ // Remove any remaining live regions
12
+ document.querySelectorAll('[aria-live]').forEach(el => {
13
+ if (document.body.contains(el)) {
14
+ document.body.removeChild(el);
15
+ }
16
+ });
17
+ });
18
+ describe('announceToScreenReader', () => {
19
+ beforeEach(() => {
20
+ jest.useFakeTimers();
21
+ });
22
+ afterEach(() => {
23
+ jest.runOnlyPendingTimers();
24
+ jest.useRealTimers();
25
+ });
26
+ it('creates a live region element with correct attributes', () => {
27
+ (0, _accessibility.announceToScreenReader)('Test message', 'polite');
28
+ const liveRegion = document.querySelector('[aria-live="polite"]');
29
+ expect(liveRegion).toBeInTheDocument();
30
+ expect(liveRegion).toHaveAttribute('aria-atomic', 'true');
31
+ expect(liveRegion).toHaveClass('sr-only');
32
+ expect(liveRegion.textContent).toBe('Test message');
33
+ });
34
+ it('uses assertive priority when specified', () => {
35
+ (0, _accessibility.announceToScreenReader)('Error message', 'assertive');
36
+ const liveRegion = document.querySelector('[aria-live="assertive"]');
37
+ expect(liveRegion).toBeInTheDocument();
38
+ expect(liveRegion.textContent).toBe('Error message');
39
+ });
40
+ it('defaults to polite priority', () => {
41
+ (0, _accessibility.announceToScreenReader)('Default message');
42
+ const liveRegion = document.querySelector('[aria-live="polite"]');
43
+ expect(liveRegion).toBeInTheDocument();
44
+ });
45
+ it('removes the announcement after default timeout', () => {
46
+ (0, _accessibility.announceToScreenReader)('Temporary message');
47
+ const liveRegion = document.querySelector('[aria-live="polite"]');
48
+ expect(liveRegion).toBeInTheDocument();
49
+
50
+ // Fast-forward past default 1000ms timeout
51
+ jest.advanceTimersByTime(1000);
52
+ expect(liveRegion).not.toBeInTheDocument();
53
+ });
54
+ it('removes the announcement after custom timeout', () => {
55
+ (0, _accessibility.announceToScreenReader)('Custom timeout message', 'polite', 2000);
56
+ const liveRegion = document.querySelector('[aria-live="polite"]');
57
+ expect(liveRegion).toBeInTheDocument();
58
+
59
+ // Should still be there after 1000ms
60
+ jest.advanceTimersByTime(1000);
61
+ expect(liveRegion).toBeInTheDocument();
62
+
63
+ // Should be gone after 2000ms
64
+ jest.advanceTimersByTime(1000);
65
+ expect(liveRegion).not.toBeInTheDocument();
66
+ });
67
+ it('returns a cleanup function', () => {
68
+ const cleanup = (0, _accessibility.announceToScreenReader)('Test cleanup');
69
+ expect(typeof cleanup).toBe('function');
70
+ const liveRegion = document.querySelector('[aria-live="polite"]');
71
+ expect(liveRegion).toBeInTheDocument();
72
+
73
+ // Call cleanup manually
74
+ cleanup();
75
+ expect(liveRegion).not.toBeInTheDocument();
76
+ });
77
+ it('handles manual cleanup safely', () => {
78
+ const cleanup = (0, _accessibility.announceToScreenReader)('Test cleanup');
79
+
80
+ // Call cleanup multiple times should not throw
81
+ expect(() => {
82
+ cleanup();
83
+ cleanup();
84
+ }).not.toThrow();
85
+ });
86
+ it('handles empty messages', () => {
87
+ (0, _accessibility.announceToScreenReader)('');
88
+ const liveRegion = document.querySelector('[aria-live="polite"]');
89
+ expect(liveRegion).toBeInTheDocument();
90
+ expect(liveRegion.textContent).toBe('');
91
+ });
92
+ it('handles null/undefined messages', () => {
93
+ expect(() => {
94
+ (0, _accessibility.announceToScreenReader)(null);
95
+ (0, _accessibility.announceToScreenReader)(undefined);
96
+ }).not.toThrow();
97
+ });
98
+ it('handles multiple simultaneous announcements', () => {
99
+ (0, _accessibility.announceToScreenReader)('First message');
100
+ (0, _accessibility.announceToScreenReader)('Second message');
101
+ const liveRegions = document.querySelectorAll('[aria-live="polite"]');
102
+ expect(liveRegions).toHaveLength(2);
103
+ expect(liveRegions[0].textContent).toBe('First message');
104
+ expect(liveRegions[1].textContent).toBe('Second message');
105
+ });
106
+ });
107
+ describe('createLiveRegion', () => {
108
+ it('creates a persistent live region with correct attributes', () => {
109
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
110
+ const element = document.querySelector('[aria-live="polite"]');
111
+ expect(element).toBeInTheDocument();
112
+ expect(element).toHaveAttribute('aria-atomic', 'true');
113
+ expect(element).toHaveClass('sr-only');
114
+ expect(element).toHaveAttribute('role', 'status');
115
+ liveRegion.destroy();
116
+ });
117
+ it('creates assertive live region with alert role', () => {
118
+ const liveRegion = (0, _accessibility.createLiveRegion)('assertive');
119
+ const element = document.querySelector('[aria-live="assertive"]');
120
+ expect(element).toBeInTheDocument();
121
+ expect(element).toHaveAttribute('role', 'alert');
122
+ liveRegion.destroy();
123
+ });
124
+ it('defaults to polite priority', () => {
125
+ const liveRegion = (0, _accessibility.createLiveRegion)();
126
+ const element = document.querySelector('[aria-live="polite"]');
127
+ expect(element).toBeInTheDocument();
128
+ liveRegion.destroy();
129
+ });
130
+ it('announces messages through the live region', () => {
131
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
132
+ const element = document.querySelector('[aria-live="polite"]');
133
+ liveRegion.announce('First message');
134
+ expect(element.textContent).toBe('First message');
135
+ liveRegion.announce('Second message');
136
+ expect(element.textContent).toBe('Second message');
137
+ liveRegion.destroy();
138
+ });
139
+ it('clears announcements after timeout', () => {
140
+ jest.useFakeTimers();
141
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
142
+ const element = document.querySelector('[aria-live="polite"]');
143
+ liveRegion.announce('Temporary message');
144
+ expect(element.textContent).toBe('Temporary message');
145
+ jest.advanceTimersByTime(1000);
146
+ expect(element.textContent).toBe('');
147
+ liveRegion.destroy();
148
+ jest.useRealTimers();
149
+ });
150
+ it('handles rapid successive announcements', () => {
151
+ jest.useFakeTimers();
152
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
153
+ const element = document.querySelector('[aria-live="polite"]');
154
+ liveRegion.announce('First');
155
+ expect(element.textContent).toBe('First');
156
+
157
+ // Announce again before timeout
158
+ jest.advanceTimersByTime(500);
159
+ liveRegion.announce('Second');
160
+ expect(element.textContent).toBe('Second');
161
+
162
+ // First timeout shouldn't clear "Second"
163
+ jest.advanceTimersByTime(500);
164
+ expect(element.textContent).toBe('Second');
165
+
166
+ // Second timeout should clear "Second"
167
+ jest.advanceTimersByTime(500);
168
+ expect(element.textContent).toBe('');
169
+ liveRegion.destroy();
170
+ jest.useRealTimers();
171
+ });
172
+ it('destroy method removes the live region', () => {
173
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
174
+ const element = document.querySelector('[aria-live="polite"]');
175
+ expect(element).toBeInTheDocument();
176
+ liveRegion.destroy();
177
+ expect(element).not.toBeInTheDocument();
178
+ });
179
+ it('handles destroy called multiple times', () => {
180
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
181
+ expect(() => {
182
+ liveRegion.destroy();
183
+ liveRegion.destroy();
184
+ }).not.toThrow();
185
+ });
186
+ it('can create multiple live regions with different priorities', () => {
187
+ const politeRegion = (0, _accessibility.createLiveRegion)('polite');
188
+ const assertiveRegion = (0, _accessibility.createLiveRegion)('assertive');
189
+ expect(document.querySelector('[aria-live="polite"]')).toBeInTheDocument();
190
+ expect(document.querySelector('[aria-live="assertive"]')).toBeInTheDocument();
191
+ politeRegion.announce('Polite message');
192
+ assertiveRegion.announce('Assertive message');
193
+ expect(document.querySelector('[aria-live="polite"]').textContent).toBe('Polite message');
194
+ expect(document.querySelector('[aria-live="assertive"]').textContent).toBe('Assertive message');
195
+ politeRegion.destroy();
196
+ assertiveRegion.destroy();
197
+ });
198
+ });
199
+ describe('safeFocus', () => {
200
+ let focusableElement;
201
+ beforeEach(() => {
202
+ // Create a focusable element for testing
203
+ focusableElement = document.createElement('input');
204
+ focusableElement.type = 'text';
205
+ focusableElement.id = 'test-input';
206
+ document.body.appendChild(focusableElement);
207
+ });
208
+ afterEach(() => {
209
+ if (document.body.contains(focusableElement)) {
210
+ document.body.removeChild(focusableElement);
211
+ }
212
+ });
213
+ it('focuses an element successfully', () => {
214
+ const result = (0, _accessibility.safeFocus)(focusableElement);
215
+ expect(result).toBe(true);
216
+ expect(document.activeElement).toBe(focusableElement);
217
+ });
218
+ it('focuses element by selector', () => {
219
+ const result = (0, _accessibility.safeFocus)('#test-input');
220
+ expect(result).toBe(true);
221
+ expect(document.activeElement).toBe(focusableElement);
222
+ });
223
+ it('returns false for non-existent selector', () => {
224
+ const result = (0, _accessibility.safeFocus)('#non-existent');
225
+ expect(result).toBe(false);
226
+ });
227
+ it('returns false for null element', () => {
228
+ const result = (0, _accessibility.safeFocus)(null);
229
+ expect(result).toBe(false);
230
+ });
231
+ it('returns false for non-focusable element', () => {
232
+ const div = document.createElement('div');
233
+ document.body.appendChild(div);
234
+ const result = (0, _accessibility.safeFocus)(div);
235
+ expect(result).toBe(false);
236
+ document.body.removeChild(div);
237
+ });
238
+ it('handles focus options', () => {
239
+ const spy = jest.spyOn(focusableElement, 'focus');
240
+ (0, _accessibility.safeFocus)(focusableElement, {
241
+ preventScroll: true
242
+ });
243
+ expect(spy).toHaveBeenCalledWith({
244
+ preventScroll: true
245
+ });
246
+ spy.mockRestore();
247
+ });
248
+ it('handles elements without focus method gracefully', () => {
249
+ const elementWithoutFocus = {
250
+ notAFocusMethod: true
251
+ };
252
+ const result = (0, _accessibility.safeFocus)(elementWithoutFocus);
253
+ expect(result).toBe(false);
254
+ });
255
+ it('handles focus errors gracefully', () => {
256
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
257
+ const elementWithBrokenFocus = {
258
+ focus: () => {
259
+ throw new Error('Focus failed');
260
+ }
261
+ };
262
+ const result = (0, _accessibility.safeFocus)(elementWithBrokenFocus);
263
+ expect(result).toBe(false);
264
+ expect(consoleSpy).toHaveBeenCalledWith('Failed to focus element:', expect.any(Error));
265
+ consoleSpy.mockRestore();
266
+ });
267
+ it('verifies focus was actually set', () => {
268
+ // Create another focusable element that's already focused
269
+ const alreadyFocused = document.createElement('input');
270
+ document.body.appendChild(alreadyFocused);
271
+ alreadyFocused.focus();
272
+
273
+ // Try to focus our test element
274
+ const result = (0, _accessibility.safeFocus)(focusableElement);
275
+ expect(result).toBe(true);
276
+ expect(document.activeElement).toBe(focusableElement);
277
+ document.body.removeChild(alreadyFocused);
278
+ });
279
+ });
280
+ describe('SSR Compatibility', () => {
281
+ let originalDocument;
282
+ beforeEach(() => {
283
+ originalDocument = global.document;
284
+ });
285
+ afterEach(() => {
286
+ global.document = originalDocument;
287
+ });
288
+ it('announceToScreenReader handles SSR environment', () => {
289
+ // Simulate SSR environment
290
+ delete global.document;
291
+ const cleanup = (0, _accessibility.announceToScreenReader)('SSR message');
292
+ expect(typeof cleanup).toBe('function');
293
+ expect(() => cleanup()).not.toThrow();
294
+ });
295
+ it('createLiveRegion handles SSR environment', () => {
296
+ // Simulate SSR environment
297
+ delete global.document;
298
+ const liveRegion = (0, _accessibility.createLiveRegion)();
299
+ expect(typeof liveRegion.announce).toBe('function');
300
+ expect(typeof liveRegion.destroy).toBe('function');
301
+ expect(() => liveRegion.announce('message')).not.toThrow();
302
+ expect(() => liveRegion.destroy()).not.toThrow();
303
+ });
304
+ });
305
+ describe('Integration Tests', () => {
306
+ it('works together for form validation announcements', () => {
307
+ // Simulate form validation scenario
308
+ const cleanup1 = (0, _accessibility.announceToScreenReader)('Form validation failed', 'assertive');
309
+ const liveRegion = (0, _accessibility.createLiveRegion)('polite');
310
+ liveRegion.announce('Checking username availability...');
311
+
312
+ // Create form field for focus testing
313
+ const input = document.createElement('input');
314
+ input.type = 'email';
315
+ document.body.appendChild(input);
316
+ const focusResult = (0, _accessibility.safeFocus)(input);
317
+ expect(focusResult).toBe(true);
318
+ expect(document.activeElement).toBe(input);
319
+ expect(document.querySelector('[aria-live="assertive"]')).toBeInTheDocument();
320
+ expect(document.querySelector('[aria-live="polite"]')).toBeInTheDocument();
321
+
322
+ // Cleanup
323
+ cleanup1();
324
+ liveRegion.destroy();
325
+ document.body.removeChild(input);
326
+ });
327
+ it('handles complex accessibility scenarios', () => {
328
+ jest.useFakeTimers();
329
+
330
+ // Multiple announcements with different priorities
331
+ const errorCleanup = (0, _accessibility.announceToScreenReader)('Error occurred', 'assertive');
332
+ const infoCleanup = (0, _accessibility.announceToScreenReader)('Processing...', 'polite');
333
+
334
+ // Persistent live region for ongoing updates
335
+ const statusRegion = (0, _accessibility.createLiveRegion)('polite');
336
+ statusRegion.announce('Step 1 of 3');
337
+
338
+ // Focus management
339
+ const button = document.createElement('button');
340
+ button.textContent = 'Next';
341
+ document.body.appendChild(button);
342
+ (0, _accessibility.safeFocus)(button);
343
+
344
+ // Verify everything is working
345
+ expect(document.querySelectorAll('[aria-live]')).toHaveLength(3);
346
+ expect(document.activeElement).toBe(button);
347
+
348
+ // Update status
349
+ statusRegion.announce('Step 2 of 3');
350
+
351
+ // Cleanup after timeout
352
+ jest.advanceTimersByTime(1000);
353
+ expect(document.querySelectorAll('[aria-live]')).toHaveLength(1); // Only persistent region remains
354
+
355
+ // Final cleanup
356
+ statusRegion.destroy();
357
+ document.body.removeChild(button);
358
+ jest.useRealTimers();
359
+ });
360
+ });
361
+ });
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+
3
+ var _inputValidation = require("../../utils/inputValidation.js");
4
+ /**
5
+ * @jest-environment jsdom
6
+ */
7
+
8
+ describe('Input Validation Utilities - Core', () => {
9
+ describe('validateLength', () => {
10
+ it('validates strings within length bounds', () => {
11
+ expect((0, _inputValidation.validateLength)('hello', 3, 10)).toBe(true);
12
+ expect((0, _inputValidation.validateLength)('hello', 5, 10)).toBe(true);
13
+ expect((0, _inputValidation.validateLength)('hello', 3, 5)).toBe(true);
14
+ });
15
+ it('rejects strings shorter than minimum length', () => {
16
+ expect((0, _inputValidation.validateLength)('hi', 5, 10)).toBe(false);
17
+ expect((0, _inputValidation.validateLength)('', 1, 10)).toBe(false);
18
+ });
19
+ it('rejects strings longer than maximum length', () => {
20
+ expect((0, _inputValidation.validateLength)('this is too long', 3, 10)).toBe(false);
21
+ expect((0, _inputValidation.validateLength)('verylongstring', 3, 5)).toBe(false);
22
+ });
23
+ it('works with only minimum length specified', () => {
24
+ expect((0, _inputValidation.validateLength)('hello', 3, null)).toBe(true);
25
+ expect((0, _inputValidation.validateLength)('hello', 3, undefined)).toBe(true);
26
+ expect((0, _inputValidation.validateLength)('hi', 5, null)).toBe(false);
27
+ });
28
+ it('works with only maximum length specified', () => {
29
+ expect((0, _inputValidation.validateLength)('hello', null, 10)).toBe(true);
30
+ expect((0, _inputValidation.validateLength)('hello', undefined, 10)).toBe(true);
31
+ expect((0, _inputValidation.validateLength)('this is too long', null, 10)).toBe(false);
32
+ });
33
+ it('returns true when no length constraints are provided', () => {
34
+ expect((0, _inputValidation.validateLength)('any string', null, null)).toBe(true);
35
+ expect((0, _inputValidation.validateLength)('any string', undefined, undefined)).toBe(true);
36
+ });
37
+ });
38
+ describe('validateRequired', () => {
39
+ it('validates non-empty strings', () => {
40
+ expect((0, _inputValidation.validateRequired)('hello')).toBe(true);
41
+ expect((0, _inputValidation.validateRequired)('a')).toBe(true);
42
+ expect((0, _inputValidation.validateRequired)('123')).toBe(true);
43
+ });
44
+ it('rejects empty strings', () => {
45
+ expect((0, _inputValidation.validateRequired)('')).toBe(false);
46
+ });
47
+ it('rejects whitespace-only strings', () => {
48
+ expect((0, _inputValidation.validateRequired)(' ')).toBe(false);
49
+ expect((0, _inputValidation.validateRequired)('\t')).toBe(false);
50
+ expect((0, _inputValidation.validateRequired)('\n')).toBe(false);
51
+ expect((0, _inputValidation.validateRequired)(' \t\n ')).toBe(false);
52
+ });
53
+ it('validates strings with leading/trailing whitespace', () => {
54
+ expect((0, _inputValidation.validateRequired)(' hello ')).toBe(true);
55
+ expect((0, _inputValidation.validateRequired)('\thello\n')).toBe(true);
56
+ });
57
+ it('handles special characters', () => {
58
+ expect((0, _inputValidation.validateRequired)('!')).toBe(true);
59
+ expect((0, _inputValidation.validateRequired)('@#$%')).toBe(true);
60
+ });
61
+ it('handles numbers as strings', () => {
62
+ expect((0, _inputValidation.validateRequired)('0')).toBe(true);
63
+ expect((0, _inputValidation.validateRequired)('123')).toBe(true);
64
+ });
65
+ });
66
+ describe('Integration Tests', () => {
67
+ it('combines validations correctly', () => {
68
+ const text = 'valid text';
69
+ expect((0, _inputValidation.validateRequired)(text)).toBe(true);
70
+ expect((0, _inputValidation.validateLength)(text, 5, 50)).toBe(true);
71
+ });
72
+ it('fails validation chain appropriately', () => {
73
+ // Empty field fails required validation
74
+ expect((0, _inputValidation.validateRequired)('')).toBe(false);
75
+
76
+ // Too short field fails length validation
77
+ expect((0, _inputValidation.validateLength)('hi', 5, 10)).toBe(false);
78
+ });
79
+ });
80
+ });