@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.
- package/dist/__tests__/AccountBox.test.js +61 -0
- package/dist/__tests__/AccountCircle.test.js +69 -0
- package/dist/__tests__/Alert.test.js +144 -0
- package/dist/__tests__/ArrowIcon.test.js +61 -0
- package/dist/__tests__/ArrowRight.test.js +12 -7
- package/dist/__tests__/BookOpen.test.js +61 -0
- package/dist/__tests__/Camera.test.js +69 -0
- package/dist/__tests__/CaretDown.test.js +69 -0
- package/dist/__tests__/ChevronDown.test.js +13 -8
- package/dist/__tests__/Comment.test.js +69 -0
- package/dist/__tests__/Ellipses.test.js +61 -0
- package/dist/__tests__/Explore.test.js +61 -0
- package/dist/__tests__/FileInput.test.js +148 -0
- package/dist/__tests__/Filter.test.js +69 -0
- package/dist/__tests__/Form.test.js +471 -0
- package/dist/__tests__/Group.test.js +61 -0
- package/dist/__tests__/GroupReview.test.js +61 -0
- package/dist/__tests__/Hamburger.test.js +69 -0
- package/dist/__tests__/Header.test.js +211 -0
- package/dist/__tests__/Heart.test.js +69 -0
- package/dist/__tests__/Home.test.js +69 -0
- package/dist/__tests__/LoadingSpinner.test.js +78 -0
- package/dist/__tests__/LogOut.test.js +69 -0
- package/dist/__tests__/Magnify.test.js +69 -0
- package/dist/__tests__/News.test.js +61 -0
- package/dist/__tests__/Review.test.js +61 -0
- package/dist/__tests__/SaveIcon.test.js +61 -0
- package/dist/__tests__/Search.test.js +101 -0
- package/dist/__tests__/utils/accessibility.test.js +361 -0
- package/dist/__tests__/utils/inputValidation-core.test.js +80 -0
- package/dist/__tests__/utils/validation-core.test.js +123 -0
- package/dist/__tests__/utils/validation.test.js +362 -0
- package/dist/components/Alert.js +104 -0
- package/dist/components/FileInput.js +96 -0
- package/dist/components/Form.js +27 -3
- package/dist/components/Header.js +1 -1
- package/dist/components/Search.js +238 -0
- package/dist/icons/AccountBox.js +33 -0
- package/dist/icons/AccountCircle.js +33 -0
- package/dist/icons/ArrowIcon.js +3 -2
- package/dist/icons/ArrowRight.js +23 -12
- package/dist/icons/BookOpen.js +33 -0
- package/dist/icons/Camera.js +33 -0
- package/dist/icons/CaretDown.js +33 -0
- package/dist/icons/ChevronDown.js +19 -9
- package/dist/icons/Comment.js +33 -0
- package/dist/icons/Explore.js +33 -0
- package/dist/icons/Filter.js +33 -0
- package/dist/icons/Group.js +33 -0
- package/dist/icons/GroupReview.js +33 -0
- package/dist/icons/Hamburger.js +14 -20
- package/dist/icons/Heart.js +33 -0
- package/dist/icons/Home.js +33 -0
- package/dist/icons/LoadingSpinner.js +3 -2
- package/dist/icons/LogOut.js +33 -0
- package/dist/icons/Magnify.js +33 -0
- package/dist/icons/News.js +33 -0
- package/dist/icons/Review.js +35 -0
- package/dist/icons/SaveIcon.js +3 -2
- package/dist/icons/index.js +112 -0
- package/dist/index.js +41 -1
- package/dist/styles/_variables.scss +5 -0
- package/dist/styles/alert.css +218 -0
- package/dist/styles/alert.css.map +1 -0
- package/dist/styles/alert.scss +128 -0
- package/dist/styles/anchor.css.map +1 -1
- package/dist/styles/avatar.css.map +1 -1
- package/dist/styles/button.css.map +1 -1
- package/dist/styles/card.css.map +1 -1
- package/dist/styles/checkbox.css.map +1 -1
- package/dist/styles/crud.css.map +1 -1
- package/dist/styles/dragAndDrop.css.map +1 -1
- package/dist/styles/error.css.map +1 -1
- package/dist/styles/file-input.css +165 -0
- package/dist/styles/file-input.css.map +1 -0
- package/dist/styles/file-input.scss +69 -0
- package/dist/styles/footer.css.map +1 -1
- package/dist/styles/form.css.map +1 -1
- package/dist/styles/header.css.map +1 -1
- package/dist/styles/heading.css.map +1 -1
- package/dist/styles/hero.css.map +1 -1
- package/dist/styles/htmlElements.css.map +1 -1
- package/dist/styles/label.css.map +1 -1
- package/dist/styles/loader.css.map +1 -1
- package/dist/styles/main.css +344 -11
- package/dist/styles/main.css.map +1 -1
- package/dist/styles/menu-hover.css.map +1 -1
- package/dist/styles/paragraph.css.map +1 -1
- package/dist/styles/prompt.css.map +1 -1
- package/dist/styles/radio.css.map +1 -1
- package/dist/styles/search.css +302 -0
- package/dist/styles/search.css.map +1 -0
- package/dist/styles/search.scss +260 -0
- package/dist/styles/select.css.map +1 -1
- package/dist/styles/side-menu.css.map +1 -1
- package/dist/styles/tabs.css.map +1 -1
- package/dist/styles/text-area.css.map +1 -1
- package/dist/styles/text-input.css +0 -11
- package/dist/styles/text-input.css.map +1 -1
- package/dist/styles/text-input.scss +0 -14
- package/dist/utils/utils/validation.js +2 -2
- package/dist/utils/validation.js +2 -2
- package/package.json +1 -1
- package/dist/icons/__tests__/CheckCircle.test.js +0 -9
- package/dist/icons/__tests__/ChevronDown.test.js +0 -9
- package/dist/icons/__tests__/Close.test.js +0 -9
- package/dist/icons/__tests__/EditSquare.test.js +0 -9
- package/dist/icons/__tests__/PlusCircle.test.js +0 -9
- 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
|
+
});
|