@muonic/muon 0.0.2-experimental-117-75fdff7.0 → 0.0.2-experimental-120-a646376.0
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/components/cta/src/cta-component.js +12 -0
- package/components/form/index.js +1 -0
- package/components/form/src/form-component.js +186 -0
- package/components/form/story.js +35 -0
- package/mixins/form-associate-mixin.js +36 -0
- package/mixins/form-element-mixin.js +29 -4
- package/mixins/validation-mixin.js +14 -6
- package/package.json +6 -6
- package/tests/components/cta/__snapshots__/cta.test.snap.js +36 -0
- package/tests/components/cta/cta.test.js +27 -1
- package/tests/components/form/__snapshots__/form.test.snap.js +139 -0
- package/tests/components/form/form.test.js +324 -0
- package/utils/scroll/index.js +31 -0
|
@@ -159,4 +159,16 @@ export class Cta extends ScopedElementsMixin(MuonElement) {
|
|
|
159
159
|
${this._wrapperElement(internal)}
|
|
160
160
|
`;
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
get submitTemplate() {
|
|
164
|
+
this.setAttribute('type', 'submit');
|
|
165
|
+
|
|
166
|
+
return this.standardTemplate;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get resetTemplate() {
|
|
170
|
+
this.setAttribute('type', 'reset');
|
|
171
|
+
|
|
172
|
+
return this.standardTemplate;
|
|
173
|
+
}
|
|
162
174
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Form } from './src/form-component.js';
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { html, MuonElement } from '@muonic/muon';
|
|
2
|
+
import scrollTo from '@muon/utils/scroll';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A form.
|
|
6
|
+
*
|
|
7
|
+
* @element form
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class Form extends MuonElement {
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this._submit = this._submit.bind(this);
|
|
15
|
+
this._reset = this._reset.bind(this);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
connectedCallback() {
|
|
19
|
+
super.connectedCallback();
|
|
20
|
+
|
|
21
|
+
queueMicrotask(() => {
|
|
22
|
+
this.__checkForFormEl();
|
|
23
|
+
if (this._nativeForm) {
|
|
24
|
+
this.__registerEvents();
|
|
25
|
+
// hack to stop browser validation pop up
|
|
26
|
+
this._nativeForm.setAttribute('novalidate', true);
|
|
27
|
+
// hack to force implicit submission (https://github.com/WICG/webcomponents/issues/187)
|
|
28
|
+
const input = document.createElement('input');
|
|
29
|
+
input.type = 'submit';
|
|
30
|
+
input.hidden = true;
|
|
31
|
+
this._nativeForm.appendChild(input);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
disconnectedCallback() {
|
|
37
|
+
super.disconnectedCallback();
|
|
38
|
+
this.__teardownEvents();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
__registerEvents() {
|
|
42
|
+
this._nativeForm?.addEventListener('submit', this._submit);
|
|
43
|
+
this._submitButton?.addEventListener('click', this._submit);
|
|
44
|
+
this._nativeForm?.addEventListener('reset', this._reset);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
__teardownEvents() {
|
|
48
|
+
this._nativeForm?.removeEventListener('submit', this._submit);
|
|
49
|
+
this._submitButton?.removeEventListener('click', this._submit);
|
|
50
|
+
this._nativeForm?.removeEventListener('reset', this._reset);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
__checkForFormEl() {
|
|
54
|
+
if (!this._nativeForm) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'No form node found. Did you put a <form> element inside?'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_reset() {
|
|
62
|
+
this.__checkForFormEl();
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
!this._resetButton.disabled ||
|
|
66
|
+
!this._resetButton.loading
|
|
67
|
+
) {
|
|
68
|
+
this._nativeForm.reset();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_submit(event) {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
|
|
76
|
+
this.__checkForFormEl();
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
!this._submitButton ||
|
|
80
|
+
this._submitButton.disabled ||
|
|
81
|
+
this._submitButton.loading
|
|
82
|
+
) {
|
|
83
|
+
return undefined; // should this be false?
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const validity = this.validate();
|
|
87
|
+
|
|
88
|
+
if (validity.isValid) {
|
|
89
|
+
this.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
90
|
+
} else {
|
|
91
|
+
const invalidElements = validity.validationStates.filter((state) => {
|
|
92
|
+
return !state.isValid;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
scrollTo({ element: invalidElements[0].formElement });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return validity.isValid;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get _nativeForm() {
|
|
102
|
+
return this.querySelector('form');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get _submitButton() {
|
|
106
|
+
return this.querySelector('button:not([hidden])[type="submit"]') ||
|
|
107
|
+
this.querySelector('input:not([hidden])[type="submit"]') ||
|
|
108
|
+
this.querySelector('*:not([hidden])[type="submit"]');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get _resetButton() {
|
|
112
|
+
return this.querySelector('button:not([hidden])[type="reset"]') ||
|
|
113
|
+
this.querySelector('input:not([hidden])[type="reset"]') ||
|
|
114
|
+
this.querySelector('*:not([hidden])[type="reset"]');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_findInputElement(element) {
|
|
118
|
+
if (element.parentElement._inputElement) {
|
|
119
|
+
return element.parentElement;
|
|
120
|
+
}
|
|
121
|
+
// Due to any layout container elements - @TODO - need better logic
|
|
122
|
+
if (element.parentElement.parentElement._inputElement) {
|
|
123
|
+
return element.parentElement.parentElement;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return element;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
validate() {
|
|
130
|
+
let isValid = true;
|
|
131
|
+
// @TODO: Check how this works with form associated
|
|
132
|
+
const validationStates = Array.from(this._nativeForm.elements).reduce((acc, element) => {
|
|
133
|
+
element = this._findInputElement(element);
|
|
134
|
+
const { name } = element;
|
|
135
|
+
const hasBeenSet = acc.filter((el) => el.name === name).length > 0;
|
|
136
|
+
|
|
137
|
+
// For checkboxes and radio button - don't set multiple times (needs checking for native inputs)
|
|
138
|
+
// Ignore buttons (including hidden reset)
|
|
139
|
+
if (
|
|
140
|
+
hasBeenSet ||
|
|
141
|
+
element === this._submitButton ||
|
|
142
|
+
element === this._resetButton ||
|
|
143
|
+
element.type === 'submit'
|
|
144
|
+
) {
|
|
145
|
+
return acc;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (element.reportValidity) {
|
|
149
|
+
element.reportValidity();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const { validity } = element;
|
|
153
|
+
|
|
154
|
+
if (validity) {
|
|
155
|
+
const { value } = element;
|
|
156
|
+
const { valid, validationMessage } = validity;
|
|
157
|
+
|
|
158
|
+
isValid = Boolean(isValid & validity.valid);
|
|
159
|
+
|
|
160
|
+
acc.push({
|
|
161
|
+
name,
|
|
162
|
+
value,
|
|
163
|
+
isValid: valid,
|
|
164
|
+
error: validationMessage,
|
|
165
|
+
validity: validity,
|
|
166
|
+
formElement: element
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return acc;
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
isValid,
|
|
175
|
+
validationStates
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get standardTemplate() {
|
|
180
|
+
return html`
|
|
181
|
+
<div class="form">
|
|
182
|
+
<slot></slot>
|
|
183
|
+
</div>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Form } from '@muonic/muon/components/form';
|
|
2
|
+
import setup from '@muonic/muon/storybook/stories';
|
|
3
|
+
|
|
4
|
+
const details = setup('form', Form);
|
|
5
|
+
|
|
6
|
+
export default details.defaultValues;
|
|
7
|
+
|
|
8
|
+
const innerDetail = () => `
|
|
9
|
+
<form>
|
|
10
|
+
<muon-inputter helper="Useful information to help populate this field." validation='["isRequired"]' name="username">
|
|
11
|
+
<label slot="label">Name</label>
|
|
12
|
+
<input type="text" placeholder="e.g. Placeholder" name="username"/>
|
|
13
|
+
</muon-inputter>
|
|
14
|
+
|
|
15
|
+
<muon-inputter value="" helper="How can we help you?" validation="["isRequired","isEmail"]" autocomplete="email">
|
|
16
|
+
<label slot="label">Email</label>
|
|
17
|
+
<input type="email" placeholder="e.g. my@email.com" autocomplete="email" name="useremail">
|
|
18
|
+
<div slot="tip-details">By providing clarification on why this information is necessary.</div>
|
|
19
|
+
</muon-inputter>
|
|
20
|
+
|
|
21
|
+
<label for="user-id">User ID<label>
|
|
22
|
+
<input type="text" id="user-id" name="user-id" required/>
|
|
23
|
+
|
|
24
|
+
<muon-inputter heading="What options do you like?" helper="How can we help you?" validation='["isRequired"]' value="b">
|
|
25
|
+
<input type="checkbox" name="checkboxes" value="a" id="check-01">
|
|
26
|
+
<label for="check-01">Option A</label>
|
|
27
|
+
<input type="checkbox" name="checkboxes" value="b" id="check-02">
|
|
28
|
+
<label for="check-02">Option B</label>
|
|
29
|
+
<div slot="tip-details">By providing clarification on why this information is necessary.</div>
|
|
30
|
+
</muon-inputter>
|
|
31
|
+
<input type="reset" />
|
|
32
|
+
<muon-cta type="submit">Submit</muon-cta>
|
|
33
|
+
<form>`;
|
|
34
|
+
|
|
35
|
+
export const Standard = (args) => details.template(args, innerDetail);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { dedupeMixin } from '@muonic/muon';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A mixin to associate the component to the enclosing native form.
|
|
5
|
+
*
|
|
6
|
+
* @mixin FormElementMixin
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const FormAssociateMixin = dedupeMixin((superClass) =>
|
|
10
|
+
class FormAssociateMixinClass extends superClass {
|
|
11
|
+
|
|
12
|
+
static get properties() {
|
|
13
|
+
return {
|
|
14
|
+
_internals: {
|
|
15
|
+
type: Object,
|
|
16
|
+
state: true
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static get formAssociated() {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this._internals = this.attachInternals();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
updated(changedProperties) {
|
|
31
|
+
if (changedProperties.has('value')) {
|
|
32
|
+
this._internals.setFormValue(this.value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
@@ -12,7 +12,8 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
12
12
|
static get properties() {
|
|
13
13
|
return {
|
|
14
14
|
name: {
|
|
15
|
-
type: String
|
|
15
|
+
type: String,
|
|
16
|
+
reflect: true
|
|
16
17
|
},
|
|
17
18
|
|
|
18
19
|
value: {
|
|
@@ -28,6 +29,11 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
28
29
|
type: String
|
|
29
30
|
},
|
|
30
31
|
|
|
32
|
+
_inputElement: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
state: true
|
|
35
|
+
},
|
|
36
|
+
|
|
31
37
|
_id: {
|
|
32
38
|
type: String,
|
|
33
39
|
state: true
|
|
@@ -55,6 +61,7 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
55
61
|
this.value = '';
|
|
56
62
|
this.labelID = '';
|
|
57
63
|
this.heading = '';
|
|
64
|
+
this._inputElement = true;
|
|
58
65
|
this._id = `${this._randomId}-input`;
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -90,6 +97,9 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
90
97
|
|
|
91
98
|
firstUpdated() {
|
|
92
99
|
super.firstUpdated();
|
|
100
|
+
if (!this.name) {
|
|
101
|
+
this.name = this._slottedInputs?.[0]?.name ?? '';
|
|
102
|
+
}
|
|
93
103
|
if (!this._isMultiple) {
|
|
94
104
|
if (this.labelID?.length > 0) {
|
|
95
105
|
this._slottedInputs.forEach((slot) => {
|
|
@@ -101,7 +111,7 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
101
111
|
this._slottedLabel?.setAttribute('for', this._id);
|
|
102
112
|
}
|
|
103
113
|
}
|
|
104
|
-
this.__syncValue();
|
|
114
|
+
this.__syncValue(true);
|
|
105
115
|
|
|
106
116
|
this._boundChangeEvent = (changeEvent) => {
|
|
107
117
|
this._onChange(changeEvent);
|
|
@@ -114,6 +124,7 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
114
124
|
this._boundInputEvent = (inputEvent) => {
|
|
115
125
|
this._onInput(inputEvent);
|
|
116
126
|
};
|
|
127
|
+
|
|
117
128
|
this._slottedInputs.forEach((input) => {
|
|
118
129
|
input.addEventListener('change', this._boundChangeEvent);
|
|
119
130
|
input.addEventListener('blur', this._boundBlurEvent);
|
|
@@ -121,13 +132,20 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
focus() {
|
|
136
|
+
this.updateComplete.then(() => {
|
|
137
|
+
this._slottedInputs[0].focus();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
124
141
|
/**
|
|
125
142
|
* A method to sync the value property of the component with value of slotted input elements.
|
|
126
143
|
*
|
|
127
|
-
* @
|
|
144
|
+
* @param {boolean} firstSync - If first time syncing values.
|
|
145
|
+
* @returns {void}
|
|
128
146
|
* @private
|
|
129
147
|
*/
|
|
130
|
-
__syncValue() {
|
|
148
|
+
__syncValue(firstSync) {
|
|
131
149
|
if (this._isMultiple) { //Check when component has slotted multi-input
|
|
132
150
|
if (!this.value && this.__checkedInput) {
|
|
133
151
|
// If component has null value and slotted input has checked value(s),
|
|
@@ -141,6 +159,9 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
141
159
|
return values.includes(input.value) && !input.checked;
|
|
142
160
|
}).forEach((input) => {
|
|
143
161
|
input.checked = true;
|
|
162
|
+
if (firstSync) {
|
|
163
|
+
input.defaultChecked = true;
|
|
164
|
+
}
|
|
144
165
|
});
|
|
145
166
|
}
|
|
146
167
|
} else { //When component has single-input slot
|
|
@@ -153,6 +174,10 @@ export const FormElementMixin = dedupeMixin((superClass) =>
|
|
|
153
174
|
// If component has not null value and slotted input has null value,
|
|
154
175
|
// assign the value of the component to value of the slotted input.
|
|
155
176
|
this._slottedInputs[0].value = this.value;
|
|
177
|
+
|
|
178
|
+
if (firstSync) {
|
|
179
|
+
this._slottedInputs[0].defaultValue = this.value;
|
|
180
|
+
}
|
|
156
181
|
}
|
|
157
182
|
}
|
|
158
183
|
}
|
|
@@ -67,6 +67,15 @@ export const ValidationMixin = dedupeMixin((superClass) =>
|
|
|
67
67
|
return !this._pristine;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
reportValidity() {
|
|
71
|
+
return this.validity;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get validity() {
|
|
75
|
+
this._pristine = false;
|
|
76
|
+
return this.validate();
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
/**
|
|
71
80
|
* A method to validate the value of the form element.
|
|
72
81
|
*
|
|
@@ -95,7 +104,7 @@ export const ValidationMixin = dedupeMixin((superClass) =>
|
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
this._validationState = validationState;
|
|
98
|
-
this.__updateAllValidity(this.
|
|
107
|
+
this.__updateAllValidity(this.validationMessage);
|
|
99
108
|
return this._slottedInputs[0].validity;
|
|
100
109
|
}
|
|
101
110
|
|
|
@@ -186,9 +195,8 @@ export const ValidationMixin = dedupeMixin((superClass) =>
|
|
|
186
195
|
* A method to get a validation message combind from the validity states.
|
|
187
196
|
*
|
|
188
197
|
* @returns {string} - Validation message.
|
|
189
|
-
* @private
|
|
190
198
|
*/
|
|
191
|
-
get
|
|
199
|
+
get validationMessage() {
|
|
192
200
|
return this._validationState?.filter((state) => {
|
|
193
201
|
return state?.value;
|
|
194
202
|
}).map((state) => {
|
|
@@ -204,12 +212,12 @@ export const ValidationMixin = dedupeMixin((superClass) =>
|
|
|
204
212
|
* @override
|
|
205
213
|
*/
|
|
206
214
|
get _addValidationMessage() {
|
|
207
|
-
if (this.showMessage && this.isDirty && this.
|
|
215
|
+
if (this.showMessage && this.isDirty && this.validationMessage) {
|
|
208
216
|
return html`
|
|
209
217
|
<div class="validation">
|
|
210
218
|
${this._addValidationIcon}
|
|
211
219
|
<div class="message">
|
|
212
|
-
${this.
|
|
220
|
+
${this.validationMessage}
|
|
213
221
|
</div>
|
|
214
222
|
</div>`;
|
|
215
223
|
}
|
|
@@ -225,7 +233,7 @@ export const ValidationMixin = dedupeMixin((superClass) =>
|
|
|
225
233
|
* @override
|
|
226
234
|
*/
|
|
227
235
|
get _addValidationListMessage() {
|
|
228
|
-
if (this.showMessage && this.isDirty && this.
|
|
236
|
+
if (this.showMessage && this.isDirty && this.validationMessage) {
|
|
229
237
|
const failedValidationStates = this._validationState?.filter((state) => {
|
|
230
238
|
return state?.value;
|
|
231
239
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muonic/muon",
|
|
3
|
-
"version": "0.0.2-experimental-
|
|
3
|
+
"version": "0.0.2-experimental-120-a646376.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"web-component-analyzer": "1.1.6"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@open-wc/testing": "3.1.
|
|
53
|
-
"@web/dev-server-esbuild": "0.
|
|
54
|
-
"@web/test-runner": "0.13.
|
|
52
|
+
"@open-wc/testing": "3.1.5",
|
|
53
|
+
"@web/dev-server-esbuild": "0.3.0",
|
|
54
|
+
"@web/test-runner": "0.13.30",
|
|
55
55
|
"@web/test-runner-browserstack": "0.5.0",
|
|
56
|
-
"@web/test-runner-playwright": "0.8.
|
|
57
|
-
"sinon": "
|
|
56
|
+
"@web/test-runner-playwright": "0.8.9",
|
|
57
|
+
"sinon": "14.0.0"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=16.13.0"
|
|
@@ -208,3 +208,39 @@ snapshots["cta implements with disabled"] =
|
|
|
208
208
|
`;
|
|
209
209
|
/* end snapshot cta implements with disabled */
|
|
210
210
|
|
|
211
|
+
snapshots["cta implements template `submit`"] =
|
|
212
|
+
`<div
|
|
213
|
+
aria-label="This is a button"
|
|
214
|
+
class="cta submit"
|
|
215
|
+
>
|
|
216
|
+
<span class="label-holder">
|
|
217
|
+
<slot>
|
|
218
|
+
</slot>
|
|
219
|
+
</span>
|
|
220
|
+
<cta-icon
|
|
221
|
+
class="icon"
|
|
222
|
+
name="arrow-right"
|
|
223
|
+
>
|
|
224
|
+
</cta-icon>
|
|
225
|
+
</div>
|
|
226
|
+
`;
|
|
227
|
+
/* end snapshot cta implements template `submit` */
|
|
228
|
+
|
|
229
|
+
snapshots["cta implements template `reset`"] =
|
|
230
|
+
`<div
|
|
231
|
+
aria-label="This is a button"
|
|
232
|
+
class="cta reset"
|
|
233
|
+
>
|
|
234
|
+
<span class="label-holder">
|
|
235
|
+
<slot>
|
|
236
|
+
</slot>
|
|
237
|
+
</span>
|
|
238
|
+
<cta-icon
|
|
239
|
+
class="icon"
|
|
240
|
+
name="arrow-right"
|
|
241
|
+
>
|
|
242
|
+
</cta-icon>
|
|
243
|
+
</div>
|
|
244
|
+
`;
|
|
245
|
+
/* end snapshot cta implements template `reset` */
|
|
246
|
+
|
|
@@ -178,9 +178,35 @@ describe('cta', () => {
|
|
|
178
178
|
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
179
179
|
expect(el.getAttribute('role')).to.not.exist; // eslint-disable-line no-unused-expressions
|
|
180
180
|
expect(el.getAttribute('tabindex')).to.not.exist; // eslint-disable-line no-unused-expressions
|
|
181
|
-
expect(cta.nodeName).to.equal('BUTTON', 'cta is a `
|
|
181
|
+
expect(cta.nodeName).to.equal('BUTTON', 'cta is a `button` element');
|
|
182
182
|
expect(cta.href).to.equal(false, 'cta has NO href');
|
|
183
183
|
expect(cta.getAttribute('tabindex')).to.equal('0', 'has tab index');
|
|
184
184
|
expect(cta.getAttribute('disabled')).to.equal('', 'cta is disabled');
|
|
185
185
|
});
|
|
186
|
+
|
|
187
|
+
it('implements template `submit`', async () => {
|
|
188
|
+
// this is to force it to act like it is in a form (aka you need a native button element)
|
|
189
|
+
const el = await fixture(html`<${tag} type="submit">This is a button</${tag}>`);
|
|
190
|
+
await defaultChecks(el);
|
|
191
|
+
|
|
192
|
+
const shadowRoot = el.shadowRoot;
|
|
193
|
+
const cta = shadowRoot.querySelector('.cta');
|
|
194
|
+
|
|
195
|
+
expect(el.type).to.equal('submit', '`type` property has default value `submit`');
|
|
196
|
+
expect(cta.href).to.equal(false, 'cta has NO href');
|
|
197
|
+
expect(cta.getAttribute('disabled')).to.equal(null, 'cta is disabled');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('implements template `reset`', async () => {
|
|
201
|
+
// this is to force it to act like it is in a form (aka you need a native button element)
|
|
202
|
+
const el = await fixture(html`<${tag} type="reset">This is a button</${tag}>`);
|
|
203
|
+
await defaultChecks(el);
|
|
204
|
+
|
|
205
|
+
const shadowRoot = el.shadowRoot;
|
|
206
|
+
const cta = shadowRoot.querySelector('.cta');
|
|
207
|
+
|
|
208
|
+
expect(el.type).to.equal('reset', '`type` property has default value `reset`');
|
|
209
|
+
expect(cta.href).to.equal(false, 'cta has NO href');
|
|
210
|
+
expect(cta.getAttribute('disabled')).to.equal(null, 'cta is disabled');
|
|
211
|
+
});
|
|
186
212
|
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/* @web/test-runner snapshot v1 */
|
|
2
|
+
export const snapshots = {};
|
|
3
|
+
|
|
4
|
+
snapshots["form implements standard self [with form]"] =
|
|
5
|
+
`<div class="form">
|
|
6
|
+
<slot>
|
|
7
|
+
</slot>
|
|
8
|
+
</div>
|
|
9
|
+
`;
|
|
10
|
+
/* end snapshot form implements standard self [with form] */
|
|
11
|
+
|
|
12
|
+
snapshots["form form with submit button"] =
|
|
13
|
+
`<div class="form">
|
|
14
|
+
<slot>
|
|
15
|
+
</slot>
|
|
16
|
+
</div>
|
|
17
|
+
`;
|
|
18
|
+
/* end snapshot form form with submit button */
|
|
19
|
+
|
|
20
|
+
snapshots["form form with submit input"] =
|
|
21
|
+
`<div class="form">
|
|
22
|
+
<slot>
|
|
23
|
+
</slot>
|
|
24
|
+
</div>
|
|
25
|
+
`;
|
|
26
|
+
/* end snapshot form form with submit input */
|
|
27
|
+
|
|
28
|
+
snapshots["form form with reset button"] =
|
|
29
|
+
`<div class="form">
|
|
30
|
+
<slot>
|
|
31
|
+
</slot>
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
/* end snapshot form form with reset button */
|
|
35
|
+
|
|
36
|
+
snapshots["form form with reset input"] =
|
|
37
|
+
`<div class="form">
|
|
38
|
+
<slot>
|
|
39
|
+
</slot>
|
|
40
|
+
</div>
|
|
41
|
+
`;
|
|
42
|
+
/* end snapshot form form with reset input */
|
|
43
|
+
|
|
44
|
+
snapshots["form form submitting"] =
|
|
45
|
+
`<div class="form">
|
|
46
|
+
<slot>
|
|
47
|
+
</slot>
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
/* end snapshot form form submitting */
|
|
51
|
+
|
|
52
|
+
snapshots["form form button disabled submitting"] =
|
|
53
|
+
`<div class="form">
|
|
54
|
+
<slot>
|
|
55
|
+
</slot>
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
/* end snapshot form form button disabled submitting */
|
|
59
|
+
|
|
60
|
+
snapshots["form form submitting validation"] =
|
|
61
|
+
`<div class="form">
|
|
62
|
+
<slot>
|
|
63
|
+
</slot>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
/* end snapshot form form submitting validation */
|
|
67
|
+
|
|
68
|
+
snapshots["form form submitting with input"] =
|
|
69
|
+
`<div class="form">
|
|
70
|
+
<slot>
|
|
71
|
+
</slot>
|
|
72
|
+
</div>
|
|
73
|
+
`;
|
|
74
|
+
/* end snapshot form form submitting with input */
|
|
75
|
+
|
|
76
|
+
snapshots["form form submitting with inputter"] =
|
|
77
|
+
`<div class="form">
|
|
78
|
+
<slot>
|
|
79
|
+
</slot>
|
|
80
|
+
</div>
|
|
81
|
+
`;
|
|
82
|
+
/* end snapshot form form submitting with inputter */
|
|
83
|
+
|
|
84
|
+
snapshots["form form submitting with inputter parent"] =
|
|
85
|
+
`<div class="form">
|
|
86
|
+
<slot>
|
|
87
|
+
</slot>
|
|
88
|
+
</div>
|
|
89
|
+
`;
|
|
90
|
+
/* end snapshot form form submitting with inputter parent */
|
|
91
|
+
|
|
92
|
+
snapshots["form form with reset cta"] =
|
|
93
|
+
`<div class="form">
|
|
94
|
+
<slot>
|
|
95
|
+
</slot>
|
|
96
|
+
</div>
|
|
97
|
+
`;
|
|
98
|
+
/* end snapshot form form with reset cta */
|
|
99
|
+
|
|
100
|
+
snapshots["form form with submit cta"] =
|
|
101
|
+
`<div class="form">
|
|
102
|
+
<slot>
|
|
103
|
+
</slot>
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
/* end snapshot form form with submit cta */
|
|
107
|
+
|
|
108
|
+
snapshots["form form button loading submitting"] =
|
|
109
|
+
`<div class="form">
|
|
110
|
+
<slot>
|
|
111
|
+
</slot>
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
114
|
+
/* end snapshot form form button loading submitting */
|
|
115
|
+
|
|
116
|
+
snapshots["form form cta loading submitting"] =
|
|
117
|
+
`<div class="form">
|
|
118
|
+
<slot>
|
|
119
|
+
</slot>
|
|
120
|
+
</div>
|
|
121
|
+
`;
|
|
122
|
+
/* end snapshot form form cta loading submitting */
|
|
123
|
+
|
|
124
|
+
snapshots["form form cta loading reset"] =
|
|
125
|
+
`<div class="form">
|
|
126
|
+
<slot>
|
|
127
|
+
</slot>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
/* end snapshot form form cta loading reset */
|
|
131
|
+
|
|
132
|
+
snapshots["form form submitting with inputter [validate]"] =
|
|
133
|
+
`<div class="form">
|
|
134
|
+
<slot>
|
|
135
|
+
</slot>
|
|
136
|
+
</div>
|
|
137
|
+
`;
|
|
138
|
+
/* end snapshot form form submitting with inputter [validate] */
|
|
139
|
+
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
|
3
|
+
import sinon from 'sinon';
|
|
4
|
+
import { defaultChecks } from '../../helpers';
|
|
5
|
+
import { Form } from '@muonic/muon/components/form';
|
|
6
|
+
import { Inputter } from '@muonic/muon/components/inputter';
|
|
7
|
+
import { Cta } from '@muonic/muon/components/cta';
|
|
8
|
+
|
|
9
|
+
const tagName = defineCE(Form);
|
|
10
|
+
const tag = unsafeStatic(tagName);
|
|
11
|
+
|
|
12
|
+
const inputterTagName = defineCE(Inputter);
|
|
13
|
+
const inputterTag = unsafeStatic(inputterTagName);
|
|
14
|
+
|
|
15
|
+
const ctaTagName = defineCE(Cta);
|
|
16
|
+
const ctaTag = unsafeStatic(ctaTagName);
|
|
17
|
+
|
|
18
|
+
describe('form', () => {
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
sinon.restore();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('implements standard self [no form]', () => {
|
|
24
|
+
const el = new Form();
|
|
25
|
+
const message = 'No form node found. Did you put a <form> element inside?';
|
|
26
|
+
expect(() => el.__checkForFormEl()).to.throw(message, 'no `form` added');
|
|
27
|
+
expect(() => el._reset()).to.throw(message, 'no `form` added for reset');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('implements standard self [with form]', async () => {
|
|
31
|
+
const el = await fixture(html`<${tag}><form></form></${tag}>`);
|
|
32
|
+
await defaultChecks(el);
|
|
33
|
+
|
|
34
|
+
const shadowRoot = el.shadowRoot;
|
|
35
|
+
const form = shadowRoot.querySelector('.form');
|
|
36
|
+
const slot = form.querySelector('slot');
|
|
37
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
38
|
+
|
|
39
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
40
|
+
expect(el._submitButton).to.equal(null, 'no `submit` button added');
|
|
41
|
+
expect(el._resetButton).to.equal(null, 'no `reset` button added');
|
|
42
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
43
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
44
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
45
|
+
expect(el._nativeForm).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('form with submit button', async () => {
|
|
49
|
+
const el = await fixture(html`<${tag}><form><button type="submit">submit</button></form></${tag}>`);
|
|
50
|
+
await defaultChecks(el);
|
|
51
|
+
|
|
52
|
+
const shadowRoot = el.shadowRoot;
|
|
53
|
+
const form = shadowRoot.querySelector('.form');
|
|
54
|
+
const slot = form.querySelector('slot');
|
|
55
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
56
|
+
|
|
57
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
58
|
+
expect(el._submitButton.nodeName).to.equal('BUTTON', 'has submit button');
|
|
59
|
+
expect(el._submitButton.type).to.equal('submit', 'has submit type button');
|
|
60
|
+
expect(el._resetButton).to.equal(null, 'no `reset` button added');
|
|
61
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
62
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
63
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('form with submit cta', async () => {
|
|
67
|
+
const el = await fixture(html`<${tag}><form><${ctaTag} type="submit">submit</${ctaTag}></form></${tag}>`);
|
|
68
|
+
await defaultChecks(el);
|
|
69
|
+
|
|
70
|
+
const shadowRoot = el.shadowRoot;
|
|
71
|
+
const form = shadowRoot.querySelector('.form');
|
|
72
|
+
const slot = form.querySelector('slot');
|
|
73
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
74
|
+
|
|
75
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
76
|
+
expect(el._submitButton.nodeName.toLowerCase()).to.equal(ctaTagName, 'has submit cta');
|
|
77
|
+
expect(el._submitButton.type).to.equal('submit', 'has submit type button');
|
|
78
|
+
expect(el._resetButton).to.equal(null, 'no `reset` cta added');
|
|
79
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
80
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
81
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('form with submit input', async () => {
|
|
85
|
+
const el = await fixture(html`<${tag}><form><input type="submit">submit</input></form></${tag}>`);
|
|
86
|
+
await defaultChecks(el);
|
|
87
|
+
|
|
88
|
+
const shadowRoot = el.shadowRoot;
|
|
89
|
+
const form = shadowRoot.querySelector('.form');
|
|
90
|
+
const slot = form.querySelector('slot');
|
|
91
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
92
|
+
|
|
93
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
94
|
+
expect(el._submitButton.nodeName).to.equal('INPUT', 'has submit input');
|
|
95
|
+
expect(el._submitButton.type).to.equal('submit', 'has submit type input');
|
|
96
|
+
expect(el._resetButton).to.equal(null, 'no `reset` button added');
|
|
97
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
98
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
99
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('form with reset button', async () => {
|
|
103
|
+
const el = await fixture(html`<${tag}><form><button type="reset">reset</button></form></${tag}>`);
|
|
104
|
+
await defaultChecks(el);
|
|
105
|
+
|
|
106
|
+
const shadowRoot = el.shadowRoot;
|
|
107
|
+
const form = shadowRoot.querySelector('.form');
|
|
108
|
+
const slot = form.querySelector('slot');
|
|
109
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
110
|
+
|
|
111
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
112
|
+
expect(el._resetButton.nodeName).to.equal('BUTTON', 'has reset button');
|
|
113
|
+
expect(el._resetButton.type).to.equal('reset', 'has reset type button');
|
|
114
|
+
expect(el._submitButton).to.equal(null, 'no `submit` button added');
|
|
115
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
116
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
117
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('form with reset cta', async () => {
|
|
121
|
+
const el = await fixture(html`<${tag}><form><${ctaTag} type="reset">reset</${ctaTag}></form></${tag}>`);
|
|
122
|
+
await defaultChecks(el);
|
|
123
|
+
|
|
124
|
+
const shadowRoot = el.shadowRoot;
|
|
125
|
+
const form = shadowRoot.querySelector('.form');
|
|
126
|
+
const slot = form.querySelector('slot');
|
|
127
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
128
|
+
|
|
129
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
130
|
+
expect(el._resetButton.nodeName.toLowerCase()).to.equal(ctaTagName, 'has reset cta');
|
|
131
|
+
expect(el._resetButton.type).to.equal('reset', 'has reset type button');
|
|
132
|
+
expect(el._submitButton).to.equal(null, 'no `submit` cta added');
|
|
133
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
134
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
135
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('form with reset input', async () => {
|
|
139
|
+
const el = await fixture(html`<${tag}><form><input type="reset">reset</input></form></${tag}>`);
|
|
140
|
+
await defaultChecks(el);
|
|
141
|
+
|
|
142
|
+
const shadowRoot = el.shadowRoot;
|
|
143
|
+
const form = shadowRoot.querySelector('.form');
|
|
144
|
+
const slot = form.querySelector('slot');
|
|
145
|
+
const hiddenSubmit = el.querySelector('input[hidden][type="submit"]');
|
|
146
|
+
|
|
147
|
+
expect(el.type).to.equal('standard', '`type` property has default value `standard`');
|
|
148
|
+
expect(el._resetButton.nodeName).to.equal('INPUT', 'has reset input');
|
|
149
|
+
expect(el._resetButton.type).to.equal('reset', 'has reset type input');
|
|
150
|
+
expect(el._submitButton).to.equal(null, 'no `submit` button added');
|
|
151
|
+
expect(form).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
152
|
+
expect(slot).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
153
|
+
expect(hiddenSubmit).to.not.be.null; // eslint-disable-line no-unused-expressions
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('form submitting', async () => {
|
|
157
|
+
const submitSpy = sinon.spy();
|
|
158
|
+
const el = await fixture(html`<${tag} @submit=${submitSpy}><form><button type="submit">submit</button></form></${tag}>`);
|
|
159
|
+
const submitBtn = el.querySelector('button');
|
|
160
|
+
|
|
161
|
+
await defaultChecks(el);
|
|
162
|
+
|
|
163
|
+
submitBtn.click();
|
|
164
|
+
|
|
165
|
+
expect(submitSpy.callCount).to.equal(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('form submitting with input', async () => {
|
|
169
|
+
const submitSpy = sinon.spy();
|
|
170
|
+
const el = await fixture(html`<${tag} @submit=${submitSpy}><form><label for="foo">Bar</label><input id="foo" type="text" required /><button type="submit">submit</button></form></${tag}>`);
|
|
171
|
+
const submitBtn = el.querySelector('button');
|
|
172
|
+
|
|
173
|
+
await defaultChecks(el);
|
|
174
|
+
|
|
175
|
+
submitBtn.click();
|
|
176
|
+
|
|
177
|
+
expect(submitSpy.callCount).to.equal(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('form submitting with inputter [validate]', async () => {
|
|
181
|
+
const submitSpy = sinon.spy();
|
|
182
|
+
const el = await fixture(html`
|
|
183
|
+
<${tag} @submit=${submitSpy}>
|
|
184
|
+
<form>
|
|
185
|
+
<${inputterTag} validation='["isRequired"]' value="foo" name="bar">
|
|
186
|
+
<label slot="label" for="foo">Bar</label>
|
|
187
|
+
<input id="foo" type="text" />
|
|
188
|
+
</${inputterTag}>
|
|
189
|
+
<button type="submit">submit</button>
|
|
190
|
+
</form>
|
|
191
|
+
</${tag}>
|
|
192
|
+
`);
|
|
193
|
+
|
|
194
|
+
const inputter = document.querySelector(inputterTagName);
|
|
195
|
+
|
|
196
|
+
await defaultChecks(el);
|
|
197
|
+
|
|
198
|
+
expect(el.validate()).to.deep.equal(
|
|
199
|
+
{
|
|
200
|
+
isValid: true,
|
|
201
|
+
validationStates:
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
name: 'bar',
|
|
205
|
+
value: 'foo',
|
|
206
|
+
error: undefined,
|
|
207
|
+
isValid: true,
|
|
208
|
+
formElement: inputter,
|
|
209
|
+
validity: inputter.validity
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('form submitting with inputter', async () => {
|
|
217
|
+
const submitSpy = sinon.spy();
|
|
218
|
+
const el = await fixture(html`
|
|
219
|
+
<${tag} @submit=${submitSpy}>
|
|
220
|
+
<form>
|
|
221
|
+
<${inputterTag} validation='["isRequired"]'>
|
|
222
|
+
<label slot="label" for="foo">Bar</label>
|
|
223
|
+
<input id="foo" type="text" />
|
|
224
|
+
</${inputterTag}>
|
|
225
|
+
<button type="submit">submit</button>
|
|
226
|
+
</form>
|
|
227
|
+
</${tag}>
|
|
228
|
+
`);
|
|
229
|
+
const submitBtn = el.querySelector('button');
|
|
230
|
+
|
|
231
|
+
await defaultChecks(el);
|
|
232
|
+
|
|
233
|
+
submitBtn.click();
|
|
234
|
+
|
|
235
|
+
expect(submitSpy.callCount).to.equal(0);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('form submitting with inputter parent', async () => {
|
|
239
|
+
const submitSpy = sinon.spy();
|
|
240
|
+
const el = await fixture(html`
|
|
241
|
+
<${tag} @submit=${submitSpy}>
|
|
242
|
+
<form>
|
|
243
|
+
<${inputterTag} heading="foo" validation='["isRequired"]'>
|
|
244
|
+
<div>
|
|
245
|
+
<input type="checkbox" name="checkboxes" value="a" id="check-01">
|
|
246
|
+
<label for="check-01">Option A</label>
|
|
247
|
+
<input type="checkbox" name="checkboxes" value="b" id="check-02">
|
|
248
|
+
<label for="check-02">Option B</label>
|
|
249
|
+
</div>
|
|
250
|
+
</${inputterTag}>
|
|
251
|
+
<button type="submit">submit</button>
|
|
252
|
+
</form>
|
|
253
|
+
</${tag}>
|
|
254
|
+
`);
|
|
255
|
+
const submitBtn = el.querySelector('button');
|
|
256
|
+
|
|
257
|
+
await defaultChecks(el);
|
|
258
|
+
|
|
259
|
+
submitBtn.click();
|
|
260
|
+
|
|
261
|
+
expect(submitSpy.callCount).to.equal(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('form button disabled submitting', async () => {
|
|
265
|
+
const submitSpy = sinon.spy();
|
|
266
|
+
const el = await fixture(html`<${tag} @submit=${submitSpy}><form><button disabled type="submit">submit</button></form></${tag}>`);
|
|
267
|
+
const submitBtn = el.querySelector('button');
|
|
268
|
+
|
|
269
|
+
await defaultChecks(el);
|
|
270
|
+
|
|
271
|
+
submitBtn.click();
|
|
272
|
+
|
|
273
|
+
expect(submitSpy.callCount).to.equal(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('form cta loading submitting', async () => {
|
|
277
|
+
const submitSpy = sinon.spy();
|
|
278
|
+
const el = await fixture(html`<${tag} @submit=${submitSpy}><form><${ctaTag} loading type="submit">submit</${ctaTag}></form></${tag}>`);
|
|
279
|
+
const submitBtn = el.querySelector(ctaTagName);
|
|
280
|
+
|
|
281
|
+
await defaultChecks(el);
|
|
282
|
+
|
|
283
|
+
submitBtn.click();
|
|
284
|
+
|
|
285
|
+
expect(submitSpy.callCount).to.equal(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('form with reset button', async () => {
|
|
289
|
+
const el = await fixture(html`<${tag}><form><label for="foo">Bar</label><input id="foo" type="text" value="foo" /><button type="reset">reset</button></form></${tag}>`);
|
|
290
|
+
const input = el.querySelector('input');
|
|
291
|
+
const resetBtn = el.querySelector('button');
|
|
292
|
+
|
|
293
|
+
await defaultChecks(el);
|
|
294
|
+
|
|
295
|
+
expect(input.value).to.equal('foo', 'default input value');
|
|
296
|
+
|
|
297
|
+
input.value = '';
|
|
298
|
+
|
|
299
|
+
expect(input.value).to.equal('', 'changed input value');
|
|
300
|
+
|
|
301
|
+
resetBtn.click();
|
|
302
|
+
|
|
303
|
+
expect(input.value).to.equal('foo', 'reset input value');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('form cta loading reset', async () => {
|
|
307
|
+
const submitSpy = sinon.spy();
|
|
308
|
+
const el = await fixture(html`<${tag} @submit=${submitSpy}><form><label for="foo">Bar</label><input id="foo" type="text" value="foo" /><${ctaTag} loading type="reset">submit</${ctaTag}></form></${tag}>`);
|
|
309
|
+
const input = el.querySelector('input');
|
|
310
|
+
const resetBtn = el.querySelector(ctaTagName);
|
|
311
|
+
|
|
312
|
+
await defaultChecks(el);
|
|
313
|
+
|
|
314
|
+
expect(input.value).to.equal('foo', 'default input value');
|
|
315
|
+
|
|
316
|
+
input.value = '';
|
|
317
|
+
|
|
318
|
+
expect(input.value).to.equal('', 'changed input value');
|
|
319
|
+
|
|
320
|
+
resetBtn.click();
|
|
321
|
+
|
|
322
|
+
expect(input.value).to.equal('', 'no reset input value');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default ({
|
|
2
|
+
element, // the element to scroll to
|
|
3
|
+
focusOn = element // optional element that gets focus
|
|
4
|
+
} = {}) => {
|
|
5
|
+
const motionQuery = window.matchMedia('(prefers-reduced-motion)');
|
|
6
|
+
|
|
7
|
+
setTimeout(function () { // Firefox needs this in order to allow the event queue to clear before scrolling
|
|
8
|
+
if (!motionQuery.matches) {
|
|
9
|
+
element.scrollIntoView({
|
|
10
|
+
behavior: 'smooth'
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}, 0);
|
|
14
|
+
|
|
15
|
+
focusOn.focus({
|
|
16
|
+
preventScroll: !motionQuery.matches
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// https://css-tricks.com/smooth-scrolling-accessibility/
|
|
20
|
+
// "In order for this to work on non-focusable target elements (section, div, span, h1-6, ect),
|
|
21
|
+
// we have to set tabindex="-1" on them"
|
|
22
|
+
|
|
23
|
+
if (focusOn !== document.activeElement) { // Checking if the target was focused
|
|
24
|
+
const tabIndex = focusOn.getAttribute('tabindex');
|
|
25
|
+
focusOn.setAttribute('tabindex', '-1'); // Adding tabindex for elements not focusable
|
|
26
|
+
focusOn.focus({
|
|
27
|
+
preventScroll: !motionQuery.matches
|
|
28
|
+
}); // Setting focus
|
|
29
|
+
focusOn.setAttribute('tabindex', tabIndex); //resetting tabindex
|
|
30
|
+
}
|
|
31
|
+
};
|