Skip to content

Commit cef7fd7

Browse files
committed
test(davinci-client): improve form field tests
1 parent 3fe0632 commit cef7fd7

14 files changed

Lines changed: 116 additions & 208 deletions

File tree

e2e/davinci-app/components/combobox.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { SingleSelectCollector, Updater } from '@forgerock/davinci-client/types';
1+
import type { MultiSelectCollector, Updater } from '@forgerock/davinci-client/types';
22

33
/**
44
* Creates a group of checkboxes with single-select behavior (like radio buttons)
@@ -7,9 +7,9 @@ import type { SingleSelectCollector, Updater } from '@forgerock/davinci-client/t
77
* @param {SingleSelectCollector} collector - Contains the options and configuration
88
* @param {Updater} updater - Function to call when selection changes
99
*/
10-
export default function checkboxComponent(
10+
export default function multiValueComponent(
1111
formEl: HTMLFormElement,
12-
collector: SingleSelectCollector,
12+
collector: MultiSelectCollector,
1313
updater: Updater,
1414
) {
1515
// Create a container for the checkboxes
@@ -22,17 +22,19 @@ export default function checkboxComponent(
2222
groupLabel.className = 'checkbox-group-label';
2323
containerDiv.appendChild(groupLabel);
2424

25-
// Track the currently selected checkbox
26-
let selectedCheckbox: HTMLInputElement | null = null;
25+
const values: string[] = [];
26+
let index = 0;
2727

2828
// Create checkboxes for each option
2929
for (const option of collector.output.options) {
3030
const wrapper = document.createElement('div');
3131
wrapper.className = 'checkbox-wrapper';
3232

33+
index += 1;
34+
3335
const checkbox = document.createElement('input');
3436
checkbox.type = 'checkbox';
35-
checkbox.id = `${collector.output.key}-${option.value}`;
37+
checkbox.id = `${collector.output.key}-${index}`;
3638
checkbox.name = collector.output.key || 'checkbox-field';
3739
checkbox.value = option.value;
3840

@@ -46,22 +48,16 @@ export default function checkboxComponent(
4648

4749
// If this checkbox is being checked
4850
if (target.checked) {
49-
// Uncheck the previously selected checkbox if there was one
50-
if (selectedCheckbox && selectedCheckbox !== target) {
51-
selectedCheckbox.checked = false;
52-
}
53-
54-
// Update the selected checkbox reference
55-
selectedCheckbox = target;
56-
57-
console.log(event.target);
58-
// Call the updater with the selected value
59-
updater(target.value);
51+
values.push(target.value);
6052
} else {
61-
// If the user is trying to uncheck the selected checkbox,
62-
// prevent that to maintain a selected state
63-
target.checked = true;
53+
// If this checkbox is being unchecked
54+
const index = values.indexOf(target.value);
55+
if (index > -1) {
56+
values.splice(index, 1);
57+
}
6458
}
59+
console.log(values);
60+
updater(values);
6561
});
6662

6763
wrapper.appendChild(checkbox);
@@ -71,28 +67,4 @@ export default function checkboxComponent(
7167

7268
// Append the container to the form
7369
formEl.appendChild(containerDiv);
74-
75-
// Add some basic styling
76-
const style = document.createElement('style');
77-
style.textContent = `
78-
.checkbox-container {
79-
margin-bottom: 15px;
80-
}
81-
82-
.checkbox-group-label {
83-
font-weight: bold;
84-
margin-bottom: 8px;
85-
}
86-
87-
.checkbox-wrapper {
88-
margin-bottom: 5px;
89-
display: flex;
90-
align-items: center;
91-
}
92-
93-
.checkbox-wrapper input[type="checkbox"] {
94-
margin-right: 8px;
95-
}
96-
`;
97-
document.head.appendChild(style);
9870
}

e2e/davinci-app/components/dropdown.ts renamed to e2e/davinci-app/components/single-value.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { SingleSelectCollector, Updater } from '@forgerock/davinci-client/t
66
* @param {SingleSelectCollector} collector - Contains the dropdown options and configuration
77
* @param {Updater} updater - Function to call when selection changes
88
*/
9-
export default function dropdownComponent(
9+
export default function singleValueComponent(
1010
formEl: HTMLFormElement,
1111
collector: SingleSelectCollector,
1212
updater: Updater,
@@ -21,13 +21,6 @@ export default function dropdownComponent(
2121
selectEl.name = collector.output.key || 'dropdown-field';
2222
selectEl.id = collector.output.key || 'dropdown-field';
2323

24-
// Add a default/placeholder option
25-
const defaultOption = document.createElement('option');
26-
defaultOption.value = '';
27-
defaultOption.textContent = 'Please select...';
28-
defaultOption.selected = true;
29-
selectEl.appendChild(defaultOption);
30-
3124
// Add all options from the data
3225
for (const option of collector.output.options) {
3326
const optionEl = document.createElement('option');
@@ -41,29 +34,10 @@ export default function dropdownComponent(
4134
// Properly type the event target
4235
const target = event.target as HTMLSelectElement;
4336
const selectedValue = target.value;
44-
updater(selectedValue); // Call the updater with the selected value
37+
updater(selectedValue);
4538
});
4639

4740
// Append elements to the form
4841
formEl.appendChild(labelEl);
4942
formEl.appendChild(selectEl);
50-
51-
// Add some basic styling
52-
const style = document.createElement('style');
53-
style.textContent = `
54-
.dropdown-label {
55-
display: block;
56-
margin-bottom: 5px;
57-
font-weight: bold;
58-
}
59-
60-
select {
61-
padding: 8px;
62-
width: 100%;
63-
border-radius: 4px;
64-
border: 1px solid #ccc;
65-
margin-bottom: 15px;
66-
}
67-
`;
68-
document.head.appendChild(style);
6943
}

e2e/davinci-app/components/text.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import type {
22
TextCollector,
33
ValidatedTextCollector,
44
Updater,
5+
Validator,
56
} from '@forgerock/davinci-client/types';
67
import { dotToCamelCase } from '../helper.js';
78

8-
export default function usernameComponent(
9+
export default function textComponent(
910
formEl: HTMLFormElement,
1011
collector: TextCollector | ValidatedTextCollector,
1112
updater: Updater,
13+
validator: Validator,
1214
) {
1315
const collectorKey = dotToCamelCase(collector.output.key);
1416
const label = document.createElement('label');
@@ -23,10 +25,32 @@ export default function usernameComponent(
2325
formEl?.appendChild(label);
2426
formEl?.appendChild(input);
2527

26-
formEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
27-
const error = updater((event.target as HTMLInputElement).value);
28-
if (error && 'error' in error) {
29-
console.error(error.error.message);
30-
}
31-
});
28+
if (collector.category === 'ValidatedSingleValueCollector') {
29+
formEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
30+
const result = validator((event.target as HTMLInputElement).value);
31+
const errorEl = formEl?.querySelector(`.${collectorKey}-error`);
32+
33+
if (Array.isArray(result) && result.length && !errorEl) {
34+
const errorEl = document.createElement('div');
35+
errorEl.className = `${collectorKey}-error`;
36+
errorEl.innerText = result.join(', ');
37+
formEl?.querySelector(`#${collectorKey}`)?.after(errorEl);
38+
} else if (Array.isArray(result) && result.length) {
39+
return;
40+
} else {
41+
formEl.querySelector(`.${collectorKey}-error`)?.remove();
42+
const error = updater((event.target as HTMLInputElement).value);
43+
if (error && 'error' in error) {
44+
console.error(error.error.message);
45+
}
46+
}
47+
});
48+
} else {
49+
formEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
50+
const error = updater((event.target as HTMLInputElement).value);
51+
if (error && 'error' in error) {
52+
console.error(error.error.message);
53+
}
54+
});
55+
}
3256
}

e2e/davinci-app/main.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import { davinci } from '@forgerock/davinci-client';
55

66
import type { DaVinciConfig, RequestMiddleware } from '@forgerock/davinci-client/types';
77

8-
import usernameComponent from './components/text.js';
8+
import textComponent from './components/text.js';
99
import passwordComponent from './components/password.js';
1010
import submitButtonComponent from './components/submit-button.js';
1111
import protect from './components/protect.js';
1212
import flowLinkComponent from './components/flow-link.js';
1313
import socialLoginButtonComponent from './components/social-login-button.js';
1414
import { serverConfigs } from './server-configs.js';
15-
import dropdownComponent from './components/dropdown.js';
16-
import comboboxComponent from './components/combobox.js';
17-
import radioComponent from './components/radio.js';
15+
import singleValueComponent from './components/single-value.js';
16+
import multiValueComponent from './components/multi-value.js';
1817

1918
const qs = window.location.search;
2019
const searchParams = new URLSearchParams(qs);
@@ -150,7 +149,6 @@ const urlParams = new URLSearchParams(window.location.search);
150149

151150
const collectors = davinciClient.getCollectors();
152151
collectors.forEach((collector) => {
153-
console.log(collector);
154152
if (collector.type === 'TextCollector' && collector.name === 'protectsdk') {
155153
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
156154
collector;
@@ -160,10 +158,11 @@ const urlParams = new URLSearchParams(window.location.search);
160158
davinciClient.update(collector), // Returns an update function for this collector
161159
);
162160
} else if (collector.type === 'TextCollector') {
163-
usernameComponent(
161+
textComponent(
164162
formEl, // You can ignore this; it's just for rendering
165163
collector, // This is the plain object of the collector
166164
davinciClient.update(collector), // Returns an update function for this collector
165+
davinciClient.validate(collector), // Returns a validate function for this collector
167166
);
168167
} else if (collector.type === 'PasswordCollector') {
169168
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
@@ -192,15 +191,10 @@ const urlParams = new URLSearchParams(window.location.search);
192191
}),
193192
renderForm, // Ignore this; it's just for re-rendering the form
194193
);
195-
} else if (
196-
collector.type === 'SingleSelectCollector' &&
197-
collector.input.type === 'DROPDOWN'
198-
) {
199-
dropdownComponent(formEl, collector, davinciClient.update(collector));
200-
} else if (collector.type === 'SingleSelectCollector' && collector.input.type === 'RADIO') {
201-
radioComponent(formEl, collector, davinciClient.update(collector));
202-
} else if (collector.type === 'MultiSelectCollector' && collector.input.type === 'COMBOBOX') {
203-
comboboxComponent(formEl, collector, davinciClient.update(collector));
194+
} else if (collector.type === 'SingleSelectCollector') {
195+
singleValueComponent(formEl, collector, davinciClient.update(collector));
196+
} else if (collector.type === 'MultiSelectCollector') {
197+
multiValueComponent(formEl, collector, davinciClient.update(collector));
204198
}
205199
});
206200

e2e/davinci-app/style.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ button:focus-visible {
104104
text-decoration: underline;
105105
}
106106

107+
.checkbox-wrapper input,
108+
.checkbox-wrapper label {
109+
display: inline-block;
110+
width: auto;
111+
vertical-align: middle;
112+
margin-right: 0.5em;
113+
}
114+
107115
@media (prefers-color-scheme: light) {
108116
:root {
109117
color: #213547;

e2e/davinci-suites/playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const config: PlaywrightTestConfig = {
1111
timeout: 30000,
1212
use: {
1313
baseURL,
14+
headless: false,
1415
ignoreHTTPSErrors: true,
1516
geolocation: { latitude: 24.9884, longitude: -87.3459 },
1617
bypassCSP: true,

0 commit comments

Comments
 (0)