@iamproperty/components 7.8.2--beta3 → 7.8.2--beta5
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/assets/css/components/actionbar.component.css +1 -1
- package/assets/css/components/actionbar.component.css.map +1 -1
- package/assets/css/components/address-lookup.component.css +1 -1
- package/assets/css/components/address-lookup.component.css.map +1 -1
- package/assets/css/components/advanced-select.component.css +1 -1
- package/assets/css/components/advanced-select.component.css.map +1 -1
- package/assets/css/components/banner.preload.css +1 -0
- package/assets/css/components/banner.preload.css.map +1 -0
- package/assets/css/components/calendar.component.css +1 -1
- package/assets/css/components/calendar.component.css.map +1 -1
- package/assets/css/components/card.component.css +1 -1
- package/assets/css/components/card.component.css.map +1 -1
- package/assets/css/components/card.module.css +1 -1
- package/assets/css/components/card.module.css.map +1 -1
- package/assets/css/components/card.preload.css +1 -0
- package/assets/css/components/card.preload.css.map +1 -0
- package/assets/css/components/carousel.component.css +1 -1
- package/assets/css/components/carousel.component.css.map +1 -1
- package/assets/css/components/carousel.config.css +1 -1
- package/assets/css/components/carousel.config.css.map +1 -1
- package/assets/css/components/config.component.css +1 -1
- package/assets/css/components/config.component.css.map +1 -1
- package/assets/css/components/content.component.css +1 -1
- package/assets/css/components/content.component.css.map +1 -1
- package/assets/css/components/fileupload.css +1 -1
- package/assets/css/components/fileupload.css.map +1 -1
- package/assets/css/components/filter-card.component.css +1 -1
- package/assets/css/components/filter-card.component.css.map +1 -1
- package/assets/css/components/modal.component.css +1 -1
- package/assets/css/components/modal.component.css.map +1 -1
- package/assets/css/components/multi-step-modal.component.css +1 -1
- package/assets/css/components/multi-step-modal.component.css.map +1 -1
- package/assets/css/components/multiselect.css +1 -1
- package/assets/css/components/multiselect.css.map +1 -1
- package/assets/css/components/nav.component.css +1 -1
- package/assets/css/components/nav.component.css.map +1 -1
- package/assets/css/components/nav.global.css +1 -1
- package/assets/css/components/nav.global.css.map +1 -1
- package/assets/css/components/notification.global.css +1 -1
- package/assets/css/components/notification.global.css.map +1 -1
- package/assets/css/components/pagination.css +1 -1
- package/assets/css/components/pagination.css.map +1 -1
- package/assets/css/components/record-card.component.css +1 -1
- package/assets/css/components/record-card.component.css.map +1 -1
- package/assets/css/components/search.component.css +1 -1
- package/assets/css/components/search.component.css.map +1 -1
- package/assets/css/components/skeleton.global.css +1 -1
- package/assets/css/components/skeleton.global.css.map +1 -1
- package/assets/css/components/slider.css +1 -1
- package/assets/css/components/slider.css.map +1 -1
- package/assets/css/components/split-button.component.css +1 -1
- package/assets/css/components/split-button.component.css.map +1 -1
- package/assets/css/components/std-nav-standalone.component.css +1 -1
- package/assets/css/components/std-nav-standalone.component.css.map +1 -1
- package/assets/css/components/tabs.component.css +1 -1
- package/assets/css/components/tabs.component.css.map +1 -1
- package/assets/css/components/tag.component.css +1 -1
- package/assets/css/components/tag.component.css.map +1 -1
- package/assets/css/components/video-card.component.css +1 -1
- package/assets/css/components/video-card.component.css.map +1 -1
- package/assets/css/components/video-modal.component.css +1 -1
- package/assets/css/components/video-modal.component.css.map +1 -1
- package/assets/css/core.min.css +1 -1
- package/assets/css/core.min.css.map +1 -1
- package/assets/css/elements/dialog.css +1 -1
- package/assets/css/elements/dialog.css.map +1 -1
- package/assets/css/elements/dropdown.css +1 -1
- package/assets/css/elements/dropdown.css.map +1 -1
- package/assets/css/elements/forms.css +1 -1
- package/assets/css/elements/forms.css.map +1 -1
- package/assets/css/elements/links--global.css +1 -1
- package/assets/css/elements/links--global.css.map +1 -1
- package/assets/css/elements/links.css +1 -1
- package/assets/css/elements/links.css.map +1 -1
- package/assets/css/style.min.css +1 -1
- package/assets/css/style.min.css.map +1 -1
- package/assets/js/components/accordion/accordion.component.min.js +1 -1
- package/assets/js/components/actionbar/actionbar.component.min.js +3 -3
- package/assets/js/components/address-lookup/address-lookup.component.min.js +4 -4
- package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
- package/assets/js/components/advanced-select/advanced-select.component.min.js +2 -2
- package/assets/js/components/applied-filters/applied-filters.component.min.js +1 -1
- package/assets/js/components/banner/banner.component.min.js +1 -1
- package/assets/js/components/barchart/barchart.component.min.js +1 -1
- package/assets/js/components/bento-grid/bento-grid.component.min.js +1 -1
- package/assets/js/components/bone/bone.component.min.js +1 -1
- package/assets/js/components/button/button.component.min.js +1 -1
- package/assets/js/components/calendar/calendar.component.min.js +2 -2
- package/assets/js/components/card/card.component.js +114 -125
- package/assets/js/components/card/card.component.min.js +7 -7
- package/assets/js/components/card/card.component.min.js.map +1 -1
- package/assets/js/components/carousel/carousel.component.js +83 -29
- package/assets/js/components/carousel/carousel.component.min.js +16 -11
- package/assets/js/components/carousel/carousel.component.min.js.map +1 -1
- package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
- package/assets/js/components/config/config.component.min.js +7 -7
- package/assets/js/components/config/config.component.min.js.map +1 -1
- package/assets/js/components/content/content.component.js +28 -69
- package/assets/js/components/content/content.component.min.js +4 -4
- package/assets/js/components/content/content.component.min.js.map +1 -1
- package/assets/js/components/darkmode/darkmode.component.min.js +1 -1
- package/assets/js/components/doughnutchart/doughnutchart.component.min.js +1 -1
- package/assets/js/components/fileupload/fileupload.component.min.js +2 -2
- package/assets/js/components/filter-card/filter-card.component.min.js +5 -5
- package/assets/js/components/filter-card/filter-card.component.min.js.map +1 -1
- package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
- package/assets/js/components/form/form.component.js +42 -151
- package/assets/js/components/form/form.component.min.js +3 -3
- package/assets/js/components/form/form.component.min.js.map +1 -1
- package/assets/js/components/header/header.component.min.js +1 -1
- package/assets/js/components/inline-edit/inline-edit.component.min.js +1 -1
- package/assets/js/components/input/input.component.min.js +1 -1
- package/assets/js/components/input-range/input-range.component.min.js +1 -1
- package/assets/js/components/marketing/marketing.component.min.js +1 -1
- package/assets/js/components/menu/menu.component.min.js +1 -1
- package/assets/js/components/milestone/milestone.component.min.js +1 -1
- package/assets/js/components/milestone-group/milestone-group.component.min.js +1 -1
- package/assets/js/components/modal/modal.component.js +16 -11
- package/assets/js/components/modal/modal.component.min.js +7 -7
- package/assets/js/components/modal/modal.component.min.js.map +1 -1
- package/assets/js/components/multi-step/multi-step.component.min.js +1 -1
- package/assets/js/components/multi-step-modal/multi-step-modal.component.min.js +4 -4
- package/assets/js/components/multiselect/multiselect.component.min.js +4 -4
- package/assets/js/components/multiselect/multiselect.component.min.js.map +1 -1
- package/assets/js/components/nav/nav.component.js +88 -79
- package/assets/js/components/nav/nav.component.min.js +8 -8
- package/assets/js/components/nav/nav.component.min.js.map +1 -1
- package/assets/js/components/notification/notification.component.min.js +2 -2
- package/assets/js/components/pagination/pagination.component.min.js +5 -5
- package/assets/js/components/password/password.component.min.js +1 -1
- package/assets/js/components/popover/popover.component.min.js +1 -1
- package/assets/js/components/rank/rank.component.min.js +1 -1
- package/assets/js/components/rankings/rankings.component.min.js +1 -1
- package/assets/js/components/rating/rating.component.min.js +1 -1
- package/assets/js/components/record-card/record-card.component.min.js +6 -6
- package/assets/js/components/record-card/record-card.component.min.js.map +1 -1
- package/assets/js/components/search/search.component.js +235 -186
- package/assets/js/components/search/search.component.min.js +12 -7
- package/assets/js/components/search/search.component.min.js.map +1 -1
- package/assets/js/components/skeleton/skeleton.component.min.js +1 -1
- package/assets/js/components/slider/slider.component.min.js +2 -2
- package/assets/js/components/split-button/split-button.component.min.js +2 -2
- package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js +5 -5
- package/assets/js/components/std-address-lookup/std-address-lookup.component.min.js.map +1 -1
- package/assets/js/components/std-nav/std-nav.component.js +12 -9
- package/assets/js/components/std-nav/std-nav.component.min.js +12 -15
- package/assets/js/components/std-nav/std-nav.component.min.js.map +1 -1
- package/assets/js/components/std-nav-standalone/std-nav-standalone.component.min.js +5 -5
- package/assets/js/components/std-nav-standalone/std-nav-standalone.component.min.js.map +1 -1
- package/assets/js/components/table/table.component.min.js +1 -1
- package/assets/js/components/table-ajax/table-ajax.component.min.js +1 -1
- package/assets/js/components/table-basic/table-basic.component.min.js +1 -1
- package/assets/js/components/table-no-submit/table-no-submit.component.min.js +1 -1
- package/assets/js/components/table-submit/table-submit.component.min.js +1 -1
- package/assets/js/components/tabs/tabs.component.min.js +4 -4
- package/assets/js/components/tag/tag.component.min.js +3 -3
- package/assets/js/components/tag/tag.component.min.js.map +1 -1
- package/assets/js/components/tooltip/tooltip.component.min.js +1 -1
- package/assets/js/components/video/video.component.min.js +1 -1
- package/assets/js/components/video-card/video-card.component.min.js +9 -9
- package/assets/js/components/video-card/video-card.component.min.js.map +1 -1
- package/assets/js/components/video-modal/video-modal.component.min.js +5 -5
- package/assets/js/components/word-count/word-count.component.min.js +1 -1
- package/assets/js/modules/card.module.js +12 -11
- package/assets/js/modules/content.js +40 -8
- package/assets/js/modules/content.test.js +62 -12
- package/assets/js/modules/data-layer.js +7 -6
- package/assets/js/modules/dropdown.js +0 -1
- package/assets/js/modules/form.js +129 -0
- package/assets/js/modules/form.test.js +132 -0
- package/assets/js/modules/nav.js +10 -3
- package/assets/js/modules/search.js +153 -0
- package/assets/js/modules/search.test.js +125 -0
- package/assets/js/modules/tabs.test.js +64 -12
- package/assets/js/modules/test-dom.js +5 -0
- package/assets/js/modules/testimonial.test.js +44 -6
- package/assets/js/modules/videos.test.js +61 -13
- package/assets/js/scripts.bundle.js +3 -3
- package/assets/js/scripts.bundle.js.map +1 -1
- package/assets/js/scripts.bundle.min.js +2 -2
- package/assets/js/scripts.bundle.min.js.map +1 -1
- package/assets/sass/_components.scss +2 -63
- package/assets/sass/_utilities.scss +1 -0
- package/assets/sass/components/banner.preload.scss +26 -0
- package/assets/sass/components/card.component.scss +1 -7
- package/assets/sass/components/card.module.scss +6 -6
- package/assets/sass/components/card.preload.scss +80 -0
- package/assets/sass/components/carousel.component.scss +165 -0
- package/assets/sass/components/carousel.config.scss +90 -249
- package/assets/sass/components/content.component.scss +0 -7
- package/assets/sass/components/modal.component.scss +5 -1
- package/assets/sass/components/nav.component.scss +2 -1
- package/assets/sass/components/nav.global.scss +0 -10
- package/assets/sass/components/notification.global.scss +8 -0
- package/assets/sass/components/search.component.scss +89 -7
- package/assets/sass/components/skeleton.global.scss +4 -0
- package/assets/sass/elements/dialog.scss +43 -0
- package/assets/sass/elements/dropdown.css +2 -0
- package/assets/sass/elements/forms.scss +0 -27
- package/assets/sass/elements/links--global.scss +40 -2
- package/assets/sass/foundations/colours.scss +0 -24
- package/assets/sass/foundations/reboot.scss +4 -0
- package/assets/sass/foundations/root.scss +0 -1
- package/assets/sass/utilities/js-display.css +2 -3
- package/assets/sass/utilities/wordpress.css +7 -0
- package/assets/ts/components/card/card.component.ts +72 -62
- package/assets/ts/components/carousel/carousel.component.ts +84 -19
- package/assets/ts/components/content/content.component.ts +36 -100
- package/assets/ts/components/form/form.component.ts +54 -213
- package/assets/ts/components/modal/modal.component.ts +27 -19
- package/assets/ts/components/nav/nav.component.ts +107 -95
- package/assets/ts/components/search/search.component.ts +260 -184
- package/assets/ts/components/std-nav/std-nav.component.ts +20 -17
- package/assets/ts/html.d.ts +6 -0
- package/assets/ts/modules/card.module.ts +19 -11
- package/assets/ts/modules/content.test.ts +84 -12
- package/assets/ts/modules/content.ts +56 -9
- package/assets/ts/modules/data-layer.ts +7 -11
- package/assets/ts/modules/dropdown.ts +0 -2
- package/assets/ts/modules/form.test.ts +183 -0
- package/assets/ts/modules/form.ts +210 -0
- package/assets/ts/modules/nav.ts +12 -3
- package/assets/ts/modules/search.test.ts +142 -0
- package/assets/ts/modules/search.ts +206 -0
- package/assets/ts/modules/tabs.test.ts +79 -12
- package/assets/ts/modules/test-dom.ts +5 -0
- package/assets/ts/modules/testimonial.test.ts +45 -6
- package/assets/ts/modules/videos.test.ts +74 -14
- package/dist/components.es.js +25 -25
- package/dist/components.umd.js +170 -163
- package/package.json +1 -1
- package/assets/js/modules/carousel.js +0 -214
- package/assets/js/modules/carousel.test.js +0 -18
- package/assets/ts/modules/carousel.test.ts +0 -27
- package/assets/ts/modules/carousel.ts +0 -301
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const getConditions = (conditions) => JSON.parse(conditions || '[]');
|
|
2
|
+
const getFormControl = (form, id) => form.querySelector(`#${id}`);
|
|
3
|
+
export const isFormValid = (form) => {
|
|
4
|
+
if (form.querySelector(':invalid'))
|
|
5
|
+
return false;
|
|
6
|
+
if (form.querySelector('.pwd-checker[data-strength="1"]') || form.querySelector('.pwd-checker[data-strength="2"]'))
|
|
7
|
+
return false;
|
|
8
|
+
if (form.querySelector('iam-multiselect[data-is-required][data-error]'))
|
|
9
|
+
return false;
|
|
10
|
+
return true;
|
|
11
|
+
};
|
|
12
|
+
export const checkConditions = (conditions, form) => {
|
|
13
|
+
let meetsCondition = true;
|
|
14
|
+
getConditions(conditions).forEach((condition) => {
|
|
15
|
+
const input = getFormControl(form, condition.if);
|
|
16
|
+
if ((input === null || input === void 0 ? void 0 : input.value) != condition.equals)
|
|
17
|
+
meetsCondition = false;
|
|
18
|
+
});
|
|
19
|
+
return meetsCondition;
|
|
20
|
+
};
|
|
21
|
+
export const showIf = (form) => {
|
|
22
|
+
form.querySelectorAll('[data-show-if]').forEach((element) => {
|
|
23
|
+
if (!checkConditions(element.getAttribute('data-show-if'), form))
|
|
24
|
+
element.classList.add('d-none');
|
|
25
|
+
else
|
|
26
|
+
element.classList.remove('d-none');
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export const hideIf = (form) => {
|
|
30
|
+
form.querySelectorAll('[data-hide-if]').forEach((element) => {
|
|
31
|
+
if (checkConditions(element.getAttribute('data-hide-if'), form))
|
|
32
|
+
element.classList.add('d-none');
|
|
33
|
+
else
|
|
34
|
+
element.classList.remove('d-none');
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
export const disabledIf = (form) => {
|
|
38
|
+
form.querySelectorAll('[data-disabled-if]').forEach((element) => {
|
|
39
|
+
if (checkConditions(element.getAttribute('data-disabled-if'), form))
|
|
40
|
+
element.setAttribute('disabled', 'disabled');
|
|
41
|
+
else
|
|
42
|
+
element.removeAttribute('disabled');
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
export const enabledIf = (form) => {
|
|
46
|
+
form.querySelectorAll('[data-enabled-if]').forEach((element) => {
|
|
47
|
+
if (!checkConditions(element.getAttribute('data-enabled-if'), form))
|
|
48
|
+
element.setAttribute('disabled', 'disabled');
|
|
49
|
+
else
|
|
50
|
+
element.removeAttribute('disabled');
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
export const requiredIf = (form) => {
|
|
54
|
+
form.querySelectorAll('[data-required-if]').forEach((element) => {
|
|
55
|
+
if (checkConditions(element.getAttribute('data-required-if'), form))
|
|
56
|
+
element.setAttribute('required', 'required');
|
|
57
|
+
else
|
|
58
|
+
element.removeAttribute('required');
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
export const readonlyIf = (form) => {
|
|
62
|
+
form.querySelectorAll('[data-readonly-if]').forEach((element) => {
|
|
63
|
+
if (checkConditions(element.getAttribute('data-readonly-if'), form))
|
|
64
|
+
element.setAttribute('readonly', 'readonly');
|
|
65
|
+
else
|
|
66
|
+
element.removeAttribute('readonly');
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
export const writeIf = (form) => {
|
|
70
|
+
form.querySelectorAll('[data-write-if]').forEach((element) => {
|
|
71
|
+
if (!checkConditions(element.getAttribute('data-write-if'), form))
|
|
72
|
+
element.setAttribute('readonly', 'readonly');
|
|
73
|
+
else
|
|
74
|
+
element.removeAttribute('readonly');
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
export const emptyIf = (form) => {
|
|
78
|
+
form.querySelectorAll('[data-empty-if]').forEach((element) => {
|
|
79
|
+
if (checkConditions(element.getAttribute('data-empty-if'), form))
|
|
80
|
+
element.value = "";
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
export const getCheckboxLimit = (element) => {
|
|
84
|
+
const limit = parseInt(element.getAttribute('data-checkbox-limit') || '10', 10);
|
|
85
|
+
return !isNaN(limit) && limit > 0 ? limit : 10;
|
|
86
|
+
};
|
|
87
|
+
export const limitCheckboxes = (event, root) => {
|
|
88
|
+
console.log(event);
|
|
89
|
+
const target = (event === null || event === void 0 ? void 0 : event.target) instanceof HTMLInputElement ? event.target : null;
|
|
90
|
+
const changedCheckbox = (target === null || target === void 0 ? void 0 : target.matches('input[type="checkbox"]')) ? target : null;
|
|
91
|
+
const checkboxLimitGroup = changedCheckbox === null || changedCheckbox === void 0 ? void 0 : changedCheckbox.closest('[data-checkbox-limit]');
|
|
92
|
+
const checkboxLimitGroups = checkboxLimitGroup
|
|
93
|
+
? [checkboxLimitGroup]
|
|
94
|
+
: [
|
|
95
|
+
...(root.hasAttribute('data-checkbox-limit') ? [root] : []),
|
|
96
|
+
...Array.from(root.querySelectorAll('[data-checkbox-limit]')),
|
|
97
|
+
];
|
|
98
|
+
checkboxLimitGroups.forEach((group) => {
|
|
99
|
+
const limit = getCheckboxLimit(group);
|
|
100
|
+
const checked = Array.from(group.querySelectorAll('input[type="checkbox"]:checked'));
|
|
101
|
+
const notChecked = Array.from(group.querySelectorAll('input[type="checkbox"]:not(:checked)'));
|
|
102
|
+
notChecked.forEach((checkbox) => {
|
|
103
|
+
checkbox.setAttribute('disabled', 'disabled');
|
|
104
|
+
});
|
|
105
|
+
if (checked.length < limit) {
|
|
106
|
+
notChecked.forEach((checkbox) => {
|
|
107
|
+
checkbox.removeAttribute('disabled');
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (checked.length == limit) {
|
|
112
|
+
// Data layer Web component created
|
|
113
|
+
const eventDetails = { element: group.hasAttribute('id') ? `#${group.getAttribute('id')}` : '', limit: limit };
|
|
114
|
+
const changeEvent = new CustomEvent('checkbox-limit-reached', { detail: eventDetails });
|
|
115
|
+
root.dispatchEvent(changeEvent);
|
|
116
|
+
const formWindow = window;
|
|
117
|
+
formWindow.dataLayer = formWindow.dataLayer || [];
|
|
118
|
+
formWindow.dataLayer.push(Object.assign({ 'event': 'checkbox-limit-reached' }, eventDetails));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if ((changedCheckbox === null || changedCheckbox === void 0 ? void 0 : changedCheckbox.checked) && group.contains(changedCheckbox)) {
|
|
122
|
+
changedCheckbox.checked = false;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
checked.slice(limit).forEach((checkbox) => {
|
|
126
|
+
checkbox.checked = false;
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, expect, it } from './test.ts';
|
|
2
|
+
import { createElement, installTestDom } from './test-dom.ts';
|
|
3
|
+
import { append } from './test-utils.ts';
|
|
4
|
+
import { checkConditions, disabledIf, emptyIf, enabledIf, getCheckboxLimit, hideIf, isFormValid, limitCheckboxes, readonlyIf, requiredIf, showIf, writeIf, } from './form.ts';
|
|
5
|
+
const { window } = installTestDom();
|
|
6
|
+
if (typeof globalThis.CustomEvent === 'undefined') {
|
|
7
|
+
globalThis.CustomEvent = class extends Event {
|
|
8
|
+
constructor(type, options = {}) {
|
|
9
|
+
super(type, options);
|
|
10
|
+
this.detail = options.detail;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const condition = (id, equals) => JSON.stringify([{ if: id, equals }]);
|
|
15
|
+
const createChangeEvent = (target) => {
|
|
16
|
+
const event = new Event('change', { bubbles: true, cancelable: true });
|
|
17
|
+
Object.defineProperty(event, 'target', { value: target });
|
|
18
|
+
return event;
|
|
19
|
+
};
|
|
20
|
+
describe('Form module', () => {
|
|
21
|
+
it('checks form validity against invalid fields, weak passwords and required multiselects', () => {
|
|
22
|
+
const form = createElement('form');
|
|
23
|
+
const originalQuerySelector = form.querySelector.bind(form);
|
|
24
|
+
form.querySelector = (selector) => (selector === ':invalid' ? createElement('input') : originalQuerySelector(selector));
|
|
25
|
+
expect(!isFormValid(form));
|
|
26
|
+
form.querySelector = originalQuerySelector;
|
|
27
|
+
append(form, createElement('div', { class: 'pwd-checker', dataStrength: '1' }));
|
|
28
|
+
expect(!isFormValid(form));
|
|
29
|
+
form.children = [];
|
|
30
|
+
append(form, createElement('iam-multiselect', { dataError: 'true', dataIsRequired: 'true' }));
|
|
31
|
+
expect(!isFormValid(form));
|
|
32
|
+
form.children = [];
|
|
33
|
+
expect(isFormValid(form));
|
|
34
|
+
});
|
|
35
|
+
it('evaluates JSON conditions against form control values', () => {
|
|
36
|
+
const form = createElement('form');
|
|
37
|
+
append(form, createElement('input', { id: 'status', value: 'active' }));
|
|
38
|
+
expect(checkConditions(condition('status', 'active'), form));
|
|
39
|
+
expect(!checkConditions(condition('status', 'archived'), form));
|
|
40
|
+
});
|
|
41
|
+
it('applies visibility conditional helpers', () => {
|
|
42
|
+
const form = createElement('form');
|
|
43
|
+
const status = createElement('input', { id: 'status', value: 'active' });
|
|
44
|
+
const shown = createElement('div', { dataShowIf: condition('status', 'active') });
|
|
45
|
+
const hidden = createElement('div', { dataHideIf: condition('status', 'active') });
|
|
46
|
+
append(form, status, shown, hidden);
|
|
47
|
+
showIf(form);
|
|
48
|
+
hideIf(form);
|
|
49
|
+
expect(!shown.classList.contains('d-none'));
|
|
50
|
+
expect(hidden.classList.contains('d-none'));
|
|
51
|
+
status.value = 'archived';
|
|
52
|
+
showIf(form);
|
|
53
|
+
hideIf(form);
|
|
54
|
+
expect(shown.classList.contains('d-none'));
|
|
55
|
+
expect(!hidden.classList.contains('d-none'));
|
|
56
|
+
});
|
|
57
|
+
it('applies enabled, disabled, required and readonly conditional helpers', () => {
|
|
58
|
+
const form = createElement('form');
|
|
59
|
+
const status = createElement('input', { id: 'status', value: 'active' });
|
|
60
|
+
const disabled = createElement('input', { dataDisabledIf: condition('status', 'active') });
|
|
61
|
+
const enabled = createElement('input', { dataEnabledIf: condition('status', 'active') });
|
|
62
|
+
const required = createElement('input', { dataRequiredIf: condition('status', 'active') });
|
|
63
|
+
const readonly = createElement('input', { dataReadonlyIf: condition('status', 'active') });
|
|
64
|
+
const writable = createElement('input', { dataWriteIf: condition('status', 'active') });
|
|
65
|
+
append(form, status, disabled, enabled, required, readonly, writable);
|
|
66
|
+
disabledIf(form);
|
|
67
|
+
enabledIf(form);
|
|
68
|
+
requiredIf(form);
|
|
69
|
+
readonlyIf(form);
|
|
70
|
+
writeIf(form);
|
|
71
|
+
expect(disabled.hasAttribute('disabled'));
|
|
72
|
+
expect(!enabled.hasAttribute('disabled'));
|
|
73
|
+
expect(required.hasAttribute('required'));
|
|
74
|
+
expect(readonly.hasAttribute('readonly'));
|
|
75
|
+
expect(!writable.hasAttribute('readonly'));
|
|
76
|
+
status.value = 'archived';
|
|
77
|
+
disabledIf(form);
|
|
78
|
+
enabledIf(form);
|
|
79
|
+
requiredIf(form);
|
|
80
|
+
readonlyIf(form);
|
|
81
|
+
writeIf(form);
|
|
82
|
+
expect(!disabled.hasAttribute('disabled'));
|
|
83
|
+
expect(enabled.hasAttribute('disabled'));
|
|
84
|
+
expect(!required.hasAttribute('required'));
|
|
85
|
+
expect(!readonly.hasAttribute('readonly'));
|
|
86
|
+
expect(writable.hasAttribute('readonly'));
|
|
87
|
+
});
|
|
88
|
+
it('empties form controls when conditions match', () => {
|
|
89
|
+
const form = createElement('form');
|
|
90
|
+
const status = createElement('input', { id: 'status', value: 'clear' });
|
|
91
|
+
const target = createElement('input', { dataEmptyIf: condition('status', 'clear'), value: 'remove me' });
|
|
92
|
+
append(form, status, target);
|
|
93
|
+
emptyIf(form);
|
|
94
|
+
expect(target.value === '');
|
|
95
|
+
});
|
|
96
|
+
it('resolves checkbox limits with a valid positive fallback', () => {
|
|
97
|
+
expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '2' })) === 2);
|
|
98
|
+
expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: '0' })) === 10);
|
|
99
|
+
expect(getCheckboxLimit(createElement('div', { dataCheckboxLimit: 'nope' })) === 10);
|
|
100
|
+
});
|
|
101
|
+
it('disables unchecked checkboxes and emits analytics when a limit is reached', () => {
|
|
102
|
+
window.dataLayer = [];
|
|
103
|
+
const form = createElement('form');
|
|
104
|
+
const group = createElement('fieldset', { dataCheckboxLimit: '2', id: 'topics' });
|
|
105
|
+
const first = createElement('input', { checked: true, type: 'checkbox' });
|
|
106
|
+
const second = createElement('input', { checked: true, type: 'checkbox' });
|
|
107
|
+
const third = createElement('input', { type: 'checkbox' });
|
|
108
|
+
let eventDetail;
|
|
109
|
+
form.addEventListener('checkbox-limit-reached', (event) => {
|
|
110
|
+
eventDetail = event.detail;
|
|
111
|
+
});
|
|
112
|
+
append(group, first, second, third);
|
|
113
|
+
append(form, group);
|
|
114
|
+
limitCheckboxes(createChangeEvent(second), form);
|
|
115
|
+
expect(third.hasAttribute('disabled'));
|
|
116
|
+
expect(eventDetail.element === '#topics');
|
|
117
|
+
expect(eventDetail.limit === 2);
|
|
118
|
+
expect(window.dataLayer[0].event === 'checkbox-limit-reached');
|
|
119
|
+
expect(window.dataLayer[0].element === '#topics');
|
|
120
|
+
});
|
|
121
|
+
it('prevents checking beyond the configured checkbox limit', () => {
|
|
122
|
+
const form = createElement('form');
|
|
123
|
+
const group = createElement('fieldset', { dataCheckboxLimit: '1' });
|
|
124
|
+
const first = createElement('input', { checked: true, type: 'checkbox' });
|
|
125
|
+
const second = createElement('input', { checked: true, type: 'checkbox' });
|
|
126
|
+
append(group, first, second);
|
|
127
|
+
append(form, group);
|
|
128
|
+
limitCheckboxes(createChangeEvent(second), form);
|
|
129
|
+
expect(first.checked);
|
|
130
|
+
expect(!second.checked);
|
|
131
|
+
});
|
|
132
|
+
});
|
package/assets/js/modules/nav.js
CHANGED
|
@@ -56,8 +56,8 @@ export const populateLinks = (data) => {
|
|
|
56
56
|
return html;
|
|
57
57
|
};
|
|
58
58
|
export const loadNavData = (Cookies) => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
-
|
|
60
|
-
const ajaxURL = '/nav.json';
|
|
59
|
+
const ajaxURL = 'https://dev.hub.iamproperty.group/data/ecosystem-switcher.json';
|
|
60
|
+
//const ajaxURL = '/nav.json';
|
|
61
61
|
// Setup controller vars if not already set
|
|
62
62
|
if (!window.controller)
|
|
63
63
|
window.controller = [];
|
|
@@ -119,7 +119,14 @@ export const loadUserData = (Cookies) => __awaiter(void 0, void 0, void 0, funct
|
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
export const setEnabledLinks = (component, data) => {
|
|
122
|
-
|
|
122
|
+
const selector = `[data-product][data-feature]`;
|
|
123
|
+
const elements = component
|
|
124
|
+
? [
|
|
125
|
+
...component.querySelectorAll(selector),
|
|
126
|
+
...(component.shadowRoot ? component.shadowRoot.querySelectorAll(selector) : []),
|
|
127
|
+
]
|
|
128
|
+
: document.querySelectorAll(`iam-nav ${selector}`);
|
|
129
|
+
elements.forEach((element) => {
|
|
123
130
|
const isEnabled = data.attributes.products[element.getAttribute('data-product')].features[element.getAttribute('data-feature')];
|
|
124
131
|
element.setAttribute('data-is-enabled', isEnabled);
|
|
125
132
|
if (isEnabled && element.getAttribute('data-enabled')) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { resolvePath, isTraversable } from './helpers';
|
|
11
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
const getResultValue = (item, key) => (isRecord(item) ? item[key] : undefined);
|
|
13
|
+
const toOptionText = (value) => String(value !== null && value !== void 0 ? value : '').replace('\n', ', ');
|
|
14
|
+
const appendDatalistOption = (datalistElement, item, valueSchema, displaySchema, groupLabel = '') => {
|
|
15
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
16
|
+
const resolvedValue = resolvePath(item, valueSchema, undefined);
|
|
17
|
+
const resolvedDisplay = resolvePath(item, displaySchema, undefined);
|
|
18
|
+
const fallbackValue = isTraversable(item) ? '' : item;
|
|
19
|
+
const actualValue = (_e = (_d = (_c = (_b = (_a = resolvedValue !== null && resolvedValue !== void 0 ? resolvedValue : getResultValue(item, 'value')) !== null && _a !== void 0 ? _a : getResultValue(item, 'id')) !== null && _b !== void 0 ? _b : resolvedDisplay) !== null && _c !== void 0 ? _c : getResultValue(item, 'title')) !== null && _d !== void 0 ? _d : getResultValue(item, 'label')) !== null && _e !== void 0 ? _e : fallbackValue;
|
|
20
|
+
const displayValue = toOptionText((_g = (_f = resolvedDisplay !== null && resolvedDisplay !== void 0 ? resolvedDisplay : getResultValue(item, 'title')) !== null && _f !== void 0 ? _f : getResultValue(item, 'label')) !== null && _g !== void 0 ? _g : actualValue);
|
|
21
|
+
if (!displayValue)
|
|
22
|
+
return;
|
|
23
|
+
const optionElement = document.createElement('option');
|
|
24
|
+
optionElement.value = String(actualValue);
|
|
25
|
+
optionElement.textContent = `${groupLabel}${displayValue}`;
|
|
26
|
+
datalistElement.appendChild(optionElement);
|
|
27
|
+
};
|
|
28
|
+
const getFormControls = (component) => Array.from(component.querySelectorAll('input,select'));
|
|
29
|
+
const getSearchSchema = (component, attributeName, fallback) => component.hasAttribute(attributeName) ? component.getAttribute(attributeName) || '' : fallback;
|
|
30
|
+
const search = (component, datalistElement, searchTerm) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
let url = component.getAttribute('data-url');
|
|
32
|
+
if (!url)
|
|
33
|
+
return;
|
|
34
|
+
const method = component.getAttribute('data-method') || 'GET';
|
|
35
|
+
const body = {};
|
|
36
|
+
const searchWindow = window;
|
|
37
|
+
// Setup controller vars if not already set
|
|
38
|
+
if (!searchWindow.controller)
|
|
39
|
+
searchWindow.controller = {};
|
|
40
|
+
// Abort if controller already present for this url
|
|
41
|
+
if (searchWindow.controller[url])
|
|
42
|
+
searchWindow.controller[url].abort();
|
|
43
|
+
// Create a new controller so it can be aborted if new fetch made
|
|
44
|
+
searchWindow.controller[url] = new AbortController();
|
|
45
|
+
const { signal } = searchWindow.controller[url];
|
|
46
|
+
const requestOptions = {
|
|
47
|
+
signal,
|
|
48
|
+
method,
|
|
49
|
+
headers: new Headers({
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
Accept: 'application/json',
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
if (method.toUpperCase() === 'GET') {
|
|
55
|
+
getFormControls(component).forEach((input) => {
|
|
56
|
+
const name = input.getAttribute('name');
|
|
57
|
+
const value = input.value;
|
|
58
|
+
if (name && value) {
|
|
59
|
+
url += `${url.includes('?') ? '&' : '?'}${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
getFormControls(component).forEach((input) => {
|
|
65
|
+
const name = input.getAttribute('name');
|
|
66
|
+
const value = input.value;
|
|
67
|
+
if (name && value) {
|
|
68
|
+
body[name] = value;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
requestOptions['body'] = JSON.stringify(body);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const response = yield fetch(url, requestOptions);
|
|
75
|
+
const responseData = (yield response.json());
|
|
76
|
+
const loopSchema = getSearchSchema(component, 'data-schema', 'data');
|
|
77
|
+
const valueSchema = getSearchSchema(component, 'data-value-schema', 'value');
|
|
78
|
+
const displaySchema = getSearchSchema(component, 'data-display-schema', 'label');
|
|
79
|
+
const loopValues = resolvePath(responseData, loopSchema, []);
|
|
80
|
+
if (Array.isArray(loopValues)) {
|
|
81
|
+
loopValues.forEach((item) => {
|
|
82
|
+
appendDatalistOption(datalistElement, item, valueSchema, displaySchema);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (isRecord(loopValues)) {
|
|
86
|
+
Object.entries(loopValues).forEach(([key, value]) => {
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
value.forEach((item) => {
|
|
89
|
+
appendDatalistOption(datalistElement, item, valueSchema, displaySchema, `${key}: `);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
filterDatalist(datalistElement, searchTerm);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.log(error);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
export const filterDatalist = (datalistElement, searchTerm) => {
|
|
101
|
+
var _a;
|
|
102
|
+
for (const optionElement of datalistElement.options) {
|
|
103
|
+
const optionText = ((_a = optionElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || optionElement.value;
|
|
104
|
+
if (optionText.toLowerCase().includes(searchTerm.toLowerCase())) {
|
|
105
|
+
optionElement.classList.remove('js-hide');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
optionElement.classList.add('js-hide');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
export const datalistSelectOption = (component, inputElement, optionElement) => {
|
|
113
|
+
var _a;
|
|
114
|
+
const datalistElement = optionElement.closest('datalist');
|
|
115
|
+
const optionText = ((_a = optionElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || optionElement.value;
|
|
116
|
+
const inputName = inputElement.getAttribute('name') || '';
|
|
117
|
+
const alternateInputName = `${inputName}Alt`;
|
|
118
|
+
inputElement.value = optionText;
|
|
119
|
+
inputElement.setAttribute('data-value', optionText);
|
|
120
|
+
//inputElement.setAttribute('data-placeholder', optionText);
|
|
121
|
+
inputElement.setAttribute('placeholder', optionText);
|
|
122
|
+
// Make sure the value of the option is passed when in a form
|
|
123
|
+
if (optionElement.value && optionElement.value !== optionText) {
|
|
124
|
+
const alternateInput = component.querySelector(`input[name="${alternateInputName}"]`);
|
|
125
|
+
if (!alternateInput)
|
|
126
|
+
component.insertAdjacentHTML('beforeend', `<input type="hidden" name="${alternateInputName}" value="${optionElement.value}">`);
|
|
127
|
+
else
|
|
128
|
+
alternateInput.value = optionElement.value;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const alternateInput = component.querySelector(`input[name="${alternateInputName}"]`);
|
|
132
|
+
if (alternateInput)
|
|
133
|
+
alternateInput.remove();
|
|
134
|
+
}
|
|
135
|
+
// Set the active value on the datalist option
|
|
136
|
+
if (!datalistElement)
|
|
137
|
+
return;
|
|
138
|
+
for (const optionLoopElement of datalistElement.options) {
|
|
139
|
+
if (optionLoopElement === optionElement)
|
|
140
|
+
optionLoopElement.classList.add('active');
|
|
141
|
+
else
|
|
142
|
+
optionLoopElement.classList.remove('active');
|
|
143
|
+
}
|
|
144
|
+
const customEvent = new CustomEvent('option-selected', {
|
|
145
|
+
detail: {
|
|
146
|
+
title: optionText,
|
|
147
|
+
value: optionElement.value || '',
|
|
148
|
+
url: optionElement.hasAttribute('data-url') ? optionElement.getAttribute('data-url') || '' : '',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
component.dispatchEvent(customEvent);
|
|
152
|
+
};
|
|
153
|
+
export default search;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { describe, expect, it } from './test.ts';
|
|
11
|
+
import { createElement, installTestDom } from './test-dom.ts';
|
|
12
|
+
import { append } from './test-utils.ts';
|
|
13
|
+
import search, { datalistSelectOption, filterDatalist } from './search.ts';
|
|
14
|
+
installTestDom();
|
|
15
|
+
if (typeof globalThis.CustomEvent === 'undefined') {
|
|
16
|
+
globalThis.CustomEvent = class extends Event {
|
|
17
|
+
constructor(type, options = {}) {
|
|
18
|
+
super(type, options);
|
|
19
|
+
this.detail = options.detail;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
describe('Search module', () => {
|
|
24
|
+
it('fetches GET results, builds datalist options and filters them by the search term', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
+
let requestedUrl = '';
|
|
26
|
+
let requestedOptions;
|
|
27
|
+
globalThis.fetch = (url, options) => {
|
|
28
|
+
requestedUrl = url;
|
|
29
|
+
requestedOptions = options;
|
|
30
|
+
return Promise.resolve({
|
|
31
|
+
json: () => Promise.resolve({
|
|
32
|
+
data: [
|
|
33
|
+
{ value: 'alpha-id', label: 'Alpha\nTeam' },
|
|
34
|
+
{ id: 'beta-id', title: 'Beta Team' },
|
|
35
|
+
],
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const component = createElement('iam-search', { dataUrl: '/results' });
|
|
40
|
+
const input = createElement('input', { name: 'q', value: 'alpha & beta' });
|
|
41
|
+
const datalist = createElement('datalist');
|
|
42
|
+
append(component, input, datalist);
|
|
43
|
+
yield search(component, datalist, 'alpha');
|
|
44
|
+
expect(requestedUrl === '/results?q=alpha%20%26%20beta');
|
|
45
|
+
expect(requestedOptions.method === 'GET');
|
|
46
|
+
expect(datalist.options.length === 2);
|
|
47
|
+
expect(datalist.options[0].value === 'alpha-id');
|
|
48
|
+
expect(datalist.options[0].textContent === 'Alpha, Team');
|
|
49
|
+
expect(!datalist.options[0].classList.contains('js-hide'));
|
|
50
|
+
expect(datalist.options[1].classList.contains('js-hide'));
|
|
51
|
+
}));
|
|
52
|
+
it('posts form values and renders grouped response options with custom schemas', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
let requestedUrl = '';
|
|
54
|
+
let requestedOptions;
|
|
55
|
+
globalThis.fetch = (url, options) => {
|
|
56
|
+
requestedUrl = url;
|
|
57
|
+
requestedOptions = options;
|
|
58
|
+
return Promise.resolve({
|
|
59
|
+
json: () => Promise.resolve({
|
|
60
|
+
results: {
|
|
61
|
+
groups: {
|
|
62
|
+
Products: [{ code: 'eco', name: 'Ecosystem' }],
|
|
63
|
+
Learning: [{ code: 'guide', name: 'Guide' }],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const component = createElement('iam-search', {
|
|
70
|
+
dataDisplaySchema: 'name',
|
|
71
|
+
dataMethod: 'POST',
|
|
72
|
+
dataSchema: 'results.groups',
|
|
73
|
+
dataUrl: '/lookup',
|
|
74
|
+
dataValueSchema: 'code',
|
|
75
|
+
});
|
|
76
|
+
const input = createElement('input', { name: 'market', value: 'auction' });
|
|
77
|
+
const select = createElement('select', { name: 'status', value: 'active' });
|
|
78
|
+
const datalist = createElement('datalist');
|
|
79
|
+
append(component, input, select, datalist);
|
|
80
|
+
yield search(component, datalist, 'guide');
|
|
81
|
+
expect(requestedUrl === '/lookup');
|
|
82
|
+
expect(requestedOptions.method === 'POST');
|
|
83
|
+
expect(requestedOptions.body === '{"market":"auction","status":"active"}');
|
|
84
|
+
expect(datalist.options.length === 2);
|
|
85
|
+
expect(datalist.options[0].value === 'eco');
|
|
86
|
+
expect(datalist.options[0].textContent === 'Products: Ecosystem');
|
|
87
|
+
expect(datalist.options[0].classList.contains('js-hide'));
|
|
88
|
+
expect(datalist.options[1].textContent === 'Learning: Guide');
|
|
89
|
+
expect(!datalist.options[1].classList.contains('js-hide'));
|
|
90
|
+
}));
|
|
91
|
+
it('filters datalist options using visible text before value', () => {
|
|
92
|
+
const datalist = createElement('datalist');
|
|
93
|
+
const visibleMatch = createElement('option', { value: 'hidden-value' }, 'Matching label');
|
|
94
|
+
const valueMatch = createElement('option', { value: 'value match' });
|
|
95
|
+
const noMatch = createElement('option', { value: 'elsewhere' }, 'Different label');
|
|
96
|
+
append(datalist, visibleMatch, valueMatch, noMatch);
|
|
97
|
+
filterDatalist(datalist, 'match');
|
|
98
|
+
expect(!visibleMatch.classList.contains('js-hide'));
|
|
99
|
+
expect(!valueMatch.classList.contains('js-hide'));
|
|
100
|
+
expect(noMatch.classList.contains('js-hide'));
|
|
101
|
+
});
|
|
102
|
+
it('selects a datalist option, stores alternate values and dispatches selection details', () => {
|
|
103
|
+
const component = createElement('iam-search');
|
|
104
|
+
const input = createElement('input', { name: 'product' });
|
|
105
|
+
const datalist = createElement('datalist');
|
|
106
|
+
const inactiveOption = createElement('option', { value: 'inactive-id' }, 'Inactive');
|
|
107
|
+
const option = createElement('option', { dataUrl: '/products/alpha', value: 'alpha-id' }, 'Alpha');
|
|
108
|
+
let selectedDetail;
|
|
109
|
+
component.addEventListener('option-selected', (event) => {
|
|
110
|
+
selectedDetail = event.detail;
|
|
111
|
+
});
|
|
112
|
+
append(datalist, inactiveOption, option);
|
|
113
|
+
append(component, input, datalist);
|
|
114
|
+
datalistSelectOption(component, input, option);
|
|
115
|
+
expect(input.value === 'Alpha');
|
|
116
|
+
expect(input.getAttribute('data-value') === 'Alpha');
|
|
117
|
+
expect(input.getAttribute('placeholder') === 'Alpha');
|
|
118
|
+
expect(component.innerHTML.includes('name="productAlt" value="alpha-id"'));
|
|
119
|
+
expect(option.classList.contains('active'));
|
|
120
|
+
expect(!inactiveOption.classList.contains('active'));
|
|
121
|
+
expect(selectedDetail.title === 'Alpha');
|
|
122
|
+
expect(selectedDetail.value === 'alpha-id');
|
|
123
|
+
expect(selectedDetail.url === '/products/alpha');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -1,23 +1,75 @@
|
|
|
1
1
|
import { describe, expect, it } from './test.ts';
|
|
2
2
|
import { createElement, installTestDom } from './test-dom.ts';
|
|
3
3
|
import { append } from './test-utils.ts';
|
|
4
|
-
import { openFirstTab, toggleTab } from './tabs.ts';
|
|
5
|
-
installTestDom();
|
|
4
|
+
import { createTabsLinks, openFirstTab, setTabsEventHandlers, toggleTab } from './tabs.ts';
|
|
5
|
+
const { window } = installTestDom();
|
|
6
|
+
const createDetail = (id, title, isOpen = false) => {
|
|
7
|
+
const detail = createElement('details', { id });
|
|
8
|
+
const summary = createElement('summary', {}, title);
|
|
9
|
+
if (isOpen)
|
|
10
|
+
detail.setAttribute('open', 'true');
|
|
11
|
+
append(detail, summary, createElement('p', {}, `${title} content`));
|
|
12
|
+
return detail;
|
|
13
|
+
};
|
|
6
14
|
describe('Tabs module', () => {
|
|
7
|
-
it('
|
|
15
|
+
it('creates tab buttons and dropdown options from direct details', () => {
|
|
16
|
+
const tabs = createElement('iam-tabs', { class: 'tabs--toggle-tags' });
|
|
17
|
+
const first = createDetail('first', 'First tab', true);
|
|
18
|
+
const second = createDetail('second', 'Second tab');
|
|
19
|
+
append(tabs, first, second);
|
|
20
|
+
createTabsLinks(tabs);
|
|
21
|
+
const buttons = tabs.querySelectorAll('.tabs__links > button');
|
|
22
|
+
const options = tabs.querySelectorAll('.tabs__dropdown > option');
|
|
23
|
+
expect(buttons.length === 2);
|
|
24
|
+
expect(buttons[0].textContent === 'First tab');
|
|
25
|
+
expect(buttons[0].getAttribute('aria-pressed') === 'true');
|
|
26
|
+
expect(buttons[0].classList.contains('tag'));
|
|
27
|
+
expect(buttons[0].getAttribute('data-id') === 'first');
|
|
28
|
+
expect(first.getAttribute('tabindex') === '-1');
|
|
29
|
+
expect(first.querySelector('summary').classList.contains('visually-hidden'));
|
|
30
|
+
expect(options[0].value === 'first-tab');
|
|
31
|
+
});
|
|
32
|
+
it('toggles matching detail panels and records tab open events', () => {
|
|
33
|
+
window.dataLayer = [];
|
|
34
|
+
const tabs = createElement('iam-tabs');
|
|
35
|
+
const first = createDetail('first', 'First tab', true);
|
|
36
|
+
const second = createDetail('second', 'Second tab');
|
|
37
|
+
const linksWrapper = createElement('div');
|
|
38
|
+
const links = createElement('div', { class: 'tabs__links' });
|
|
39
|
+
const dropdownWrapper = createElement('div');
|
|
40
|
+
const dropdown = createElement('select', { class: 'tabs__dropdown' });
|
|
41
|
+
append(links, createElement('button', { ariaPressed: 'true', dataIndex: '0' }, 'First tab'));
|
|
42
|
+
append(links, createElement('button', { dataIndex: '1' }, 'Second tab'));
|
|
43
|
+
append(linksWrapper, links);
|
|
44
|
+
append(dropdownWrapper, dropdown);
|
|
45
|
+
append(tabs, linksWrapper, dropdownWrapper, first, second);
|
|
46
|
+
setTabsEventHandlers(tabs);
|
|
47
|
+
const buttons = tabs.querySelectorAll('.tabs__links > button');
|
|
48
|
+
buttons[1].click();
|
|
49
|
+
expect(!first.hasAttribute('open'));
|
|
50
|
+
expect(second.getAttribute('open') === 'true');
|
|
51
|
+
expect(buttons[1].getAttribute('aria-pressed') === 'true');
|
|
52
|
+
expect(window.dataLayer[0].event === 'openTab');
|
|
53
|
+
expect(window.dataLayer[0].tabTitle === 'Second tab');
|
|
54
|
+
});
|
|
55
|
+
it('opens the tab matching the current location hash', () => {
|
|
8
56
|
const tabs = createElement('iam-tabs');
|
|
9
|
-
const first = createElement('details');
|
|
10
|
-
const second = createElement('details');
|
|
11
|
-
const buttonOne = createElement('button', { dataIndex: '0' });
|
|
12
|
-
const buttonTwo = createElement('button', { dataIndex: '1' });
|
|
13
57
|
tabs.shadowRoot = createElement('shadow-root');
|
|
14
58
|
const links = createElement('div', { class: 'tabs__links' });
|
|
15
|
-
append(links,
|
|
59
|
+
append(links, createElement('button', { dataId: 'first' }), createElement('button', { dataId: 'second' }));
|
|
16
60
|
append(tabs.shadowRoot, links);
|
|
17
|
-
append(tabs, first, second);
|
|
18
|
-
|
|
61
|
+
append(tabs, createDetail('first', 'First tab'), createDetail('second', 'Second tab'));
|
|
62
|
+
window.location.hash = '#second';
|
|
19
63
|
openFirstTab(tabs);
|
|
20
|
-
expect(
|
|
21
|
-
expect(second.
|
|
64
|
+
expect(tabs.querySelector('details[id="second"]').getAttribute('open') === 'true');
|
|
65
|
+
expect(tabs.shadowRoot.querySelector('[data-id="second"]').getAttribute('aria-pressed') === 'true');
|
|
66
|
+
window.location.hash = '';
|
|
67
|
+
});
|
|
68
|
+
it('toggles detail panels directly from a selected tab button', () => {
|
|
69
|
+
const details = [createDetail('first', 'First tab'), createDetail('second', 'Second tab')];
|
|
70
|
+
const button = createElement('button', { dataIndex: '1' });
|
|
71
|
+
toggleTab(details, button);
|
|
72
|
+
expect(!details[0].hasAttribute('open'));
|
|
73
|
+
expect(details[1].getAttribute('open') === 'true');
|
|
22
74
|
});
|
|
23
75
|
});
|