Skip to content

Commit 6f0322d

Browse files
authored
Merge pull request #474 from ForgeRock/journey-test-migration
test(journey-client): ported legacy Journey tests to new Client
2 parents 9909778 + b02ef84 commit 6f0322d

50 files changed

Lines changed: 1650 additions & 188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

e2e/journey-app/callback-map.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
8+
import type {
9+
AttributeInputCallback,
10+
BaseCallback,
11+
ChoiceCallback,
12+
ConfirmationCallback,
13+
DeviceProfileCallback,
14+
HiddenValueCallback,
15+
KbaCreateCallback,
16+
MetadataCallback,
17+
NameCallback,
18+
PasswordCallback,
19+
PingOneProtectEvaluationCallback,
20+
PingOneProtectInitializeCallback,
21+
PollingWaitCallback,
22+
ReCaptchaCallback,
23+
ReCaptchaEnterpriseCallback,
24+
RedirectCallback,
25+
SelectIdPCallback,
26+
SuspendedTextOutputCallback,
27+
TermsAndConditionsCallback,
28+
TextInputCallback,
29+
TextOutputCallback,
30+
ValidatedCreatePasswordCallback,
31+
ValidatedCreateUsernameCallback,
32+
} from '@forgerock/journey-client/types';
33+
34+
import {
35+
attributeInputComponent,
36+
choiceComponent,
37+
confirmationComponent,
38+
deviceProfileComponent,
39+
hiddenValueComponent,
40+
kbaCreateComponent,
41+
metadataComponent,
42+
passwordComponent,
43+
pingProtectEvaluationComponent,
44+
pingProtectInitializeComponent,
45+
pollingWaitComponent,
46+
recaptchaComponent,
47+
recaptchaEnterpriseComponent,
48+
redirectComponent,
49+
selectIdpComponent,
50+
suspendedTextOutputComponent,
51+
termsAndConditionsComponent,
52+
textInputComponent,
53+
textOutputComponent,
54+
validatedPasswordComponent,
55+
validatedUsernameComponent,
56+
} from './components/index.js';
57+
58+
/**
59+
* Renders a callback component based on its type
60+
* @param journeyEl - The container element to append the component to
61+
* @param callback - The callback instance
62+
* @param idx - Index for generating unique IDs
63+
*/
64+
export function renderCallback(
65+
journeyEl: HTMLDivElement,
66+
callback: BaseCallback,
67+
idx: number,
68+
): void {
69+
switch (callback.getType()) {
70+
case 'BooleanAttributeInputCallback':
71+
case 'NumberAttributeInputCallback':
72+
case 'StringAttributeInputCallback':
73+
attributeInputComponent(
74+
journeyEl,
75+
callback as AttributeInputCallback<string | number | boolean>,
76+
idx,
77+
);
78+
break;
79+
case 'ChoiceCallback':
80+
choiceComponent(journeyEl, callback as ChoiceCallback, idx);
81+
break;
82+
case 'ConfirmationCallback':
83+
confirmationComponent(journeyEl, callback as ConfirmationCallback, idx);
84+
break;
85+
case 'DeviceProfileCallback':
86+
deviceProfileComponent(journeyEl, callback as DeviceProfileCallback, idx);
87+
break;
88+
case 'HiddenValueCallback':
89+
hiddenValueComponent(journeyEl, callback as HiddenValueCallback, idx);
90+
break;
91+
case 'KbaCreateCallback':
92+
kbaCreateComponent(journeyEl, callback as KbaCreateCallback, idx);
93+
break;
94+
case 'MetadataCallback':
95+
metadataComponent(journeyEl, callback as MetadataCallback, idx);
96+
break;
97+
case 'NameCallback':
98+
textInputComponent(journeyEl, callback as NameCallback, idx);
99+
break;
100+
case 'PasswordCallback':
101+
passwordComponent(journeyEl, callback as PasswordCallback, idx);
102+
break;
103+
case 'PingOneProtectEvaluationCallback':
104+
pingProtectEvaluationComponent(journeyEl, callback as PingOneProtectEvaluationCallback, idx);
105+
break;
106+
case 'PingOneProtectInitializeCallback':
107+
pingProtectInitializeComponent(journeyEl, callback as PingOneProtectInitializeCallback, idx);
108+
break;
109+
case 'PollingWaitCallback':
110+
pollingWaitComponent(journeyEl, callback as PollingWaitCallback, idx);
111+
break;
112+
case 'ReCaptchaCallback':
113+
recaptchaComponent(journeyEl, callback as ReCaptchaCallback, idx);
114+
break;
115+
case 'ReCaptchaEnterpriseCallback':
116+
recaptchaEnterpriseComponent(journeyEl, callback as ReCaptchaEnterpriseCallback, idx);
117+
break;
118+
case 'RedirectCallback':
119+
redirectComponent(journeyEl, callback as RedirectCallback, idx);
120+
break;
121+
case 'SelectIdPCallback':
122+
selectIdpComponent(journeyEl, callback as SelectIdPCallback, idx);
123+
break;
124+
case 'SuspendedTextOutputCallback':
125+
suspendedTextOutputComponent(journeyEl, callback as SuspendedTextOutputCallback, idx);
126+
break;
127+
case 'TermsAndConditionsCallback':
128+
termsAndConditionsComponent(journeyEl, callback as TermsAndConditionsCallback, idx);
129+
break;
130+
case 'TextInputCallback':
131+
textInputComponent(journeyEl, callback as TextInputCallback, idx);
132+
break;
133+
case 'TextOutputCallback':
134+
textOutputComponent(journeyEl, callback as TextOutputCallback, idx);
135+
break;
136+
case 'ValidatedCreatePasswordCallback':
137+
validatedPasswordComponent(journeyEl, callback as ValidatedCreatePasswordCallback, idx);
138+
break;
139+
case 'ValidatedCreateUsernameCallback':
140+
validatedUsernameComponent(journeyEl, callback as ValidatedCreateUsernameCallback, idx);
141+
break;
142+
default:
143+
console.warn(`Unknown callback type: ${callback.getType()}`);
144+
break;
145+
}
146+
}
147+
148+
/**
149+
* Renders all callbacks in a step
150+
* @param journeyEl - The container element to append components to
151+
* @param callbacks - Array of callback instances
152+
*/
153+
export function renderCallbacks(journeyEl: HTMLDivElement, callbacks: BaseCallback[]): void {
154+
callbacks.forEach((callback, idx) => {
155+
renderCallback(journeyEl, callback, idx);
156+
});
157+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Journey App Components
2+
3+
This directory contains UI components for rendering different types of journey callbacks in the e2e test application. Each component follows a consistent pattern and handles the specific requirements of its callback type.
4+
5+
## Available Components
6+
7+
### Input Components
8+
9+
- **`attribute-input.ts`** - `AttributeInputCallback` - Handles string, number, and boolean attribute inputs with appropriate input types
10+
- **`choice.ts`** - `ChoiceCallback` - Renders a select dropdown with available choices
11+
- **`confirmation.ts`** - `ConfirmationCallback` - Creates radio buttons for confirmation options
12+
- **`kba-create.ts`** - `KbaCreateCallback` - Two-field form for creating security questions and answers
13+
- **`password.ts`** - `PasswordCallback` - Password input field
14+
- **`text-input.ts`** - `NameCallback`, `TextInputCallback` - Generic text input component
15+
- **`validated-password.ts`** - `ValidatedCreatePasswordCallback` - Password input with validation
16+
- **`validated-username.ts`** - `ValidatedCreateUsernameCallback` - Username input with validation
17+
18+
### Output Components
19+
20+
- **`text-output.ts`** - `TextOutputCallback` - Displays text messages
21+
- **`suspended-text-output.ts`** - `SuspendedTextOutputCallback` - Styled suspension message display
22+
23+
### Interaction Components
24+
25+
- **`redirect.ts`** - `RedirectCallback` - Button to trigger external redirects
26+
- **`select-idp.ts`** - `SelectIdPCallback` - Radio buttons for identity provider selection
27+
- **`terms-and-conditions.ts`** - `TermsAndConditionsCallback` - Terms display with acceptance checkbox
28+
29+
### Security Components
30+
31+
- **`recaptcha.ts`** - `ReCaptchaCallback` - reCAPTCHA challenge placeholder
32+
- **`recaptcha-enterprise.ts`** - `ReCaptchaEnterpriseCallback` - reCAPTCHA Enterprise placeholder
33+
- **`ping-protect-evaluation.ts`** - `PingOneProtectEvaluationCallback` - Risk assessment display
34+
- **`ping-protect-initialize.ts`** - `PingOneProtectInitializeCallback` - Protection initialization
35+
36+
### Utility Components
37+
38+
- **`device-profile.ts`** - `DeviceProfileCallback` - Device profiling indicator
39+
- **`hidden-value.ts`** - `HiddenValueCallback` - Hidden input field
40+
- **`metadata.ts`** - `MetadataCallback` - Hidden metadata storage
41+
- **`polling-wait.ts`** - `PollingWaitCallback` - Loading spinner with wait message
42+
43+
## Component Pattern
44+
45+
All components follow this consistent pattern:
46+
47+
```typescript
48+
export default function componentName(
49+
journeyEl: HTMLDivElement,
50+
callback: CallbackType,
51+
idx: number,
52+
) {
53+
// Create DOM elements
54+
// Set up event listeners
55+
// Append to journeyEl
56+
}
57+
```
58+
59+
### Parameters
60+
61+
- **`journeyEl`** - The container element to append the component to
62+
- **`callback`** - The callback instance with data and methods
63+
- **`idx`** - Index for generating unique IDs
64+
65+
### Usage Example
66+
67+
```typescript
68+
import { choiceComponent } from './components/index.js';
69+
70+
// Render a choice callback
71+
choiceComponent(containerDiv, choiceCallback, 0);
72+
```
73+
74+
## Implementation Notes
75+
76+
- All components handle their own styling via inline CSS or style attributes
77+
- Event listeners are set up to call appropriate callback methods
78+
- Components generate unique IDs using the callback's input name or a fallback
79+
- Error handling is implemented where appropriate
80+
- Console logging is used for debugging and demonstration
81+
82+
## Component States
83+
84+
Some components like reCAPTCHA and PingOne Protect include simulation timeouts for demonstration purposes in the e2e testing environment. In production, these would integrate with actual third-party services.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { AttributeInputCallback } from '@forgerock/journey-client/types';
8+
9+
export default function attributeInputComponent(
10+
journeyEl: HTMLDivElement,
11+
callback: AttributeInputCallback<string | number | boolean>,
12+
idx: number,
13+
) {
14+
const collectorKey = callback?.payload?.input?.[0].name || `collector-${idx}`;
15+
const label = document.createElement('label');
16+
const input = document.createElement('input');
17+
18+
label.htmlFor = collectorKey;
19+
label.innerText = callback.getPrompt();
20+
21+
// Determine input type based on attribute type
22+
const attributeType = callback.getType();
23+
if (attributeType === 'BooleanAttributeInputCallback') {
24+
input.type = 'checkbox';
25+
input.checked = (callback.getInputValue() as boolean) || false;
26+
} else if (attributeType === 'NumberAttributeInputCallback') {
27+
input.type = 'number';
28+
input.value = String(callback.getInputValue() || '');
29+
} else {
30+
input.type = 'text';
31+
input.value = String(callback.getInputValue() || '');
32+
}
33+
34+
input.id = collectorKey;
35+
input.name = collectorKey;
36+
input.required = callback.isRequired();
37+
38+
journeyEl?.appendChild(label);
39+
journeyEl?.appendChild(input);
40+
41+
journeyEl?.querySelector(`#${collectorKey}`)?.addEventListener('input', (event) => {
42+
const target = event.target as HTMLInputElement;
43+
if (attributeType === 'BooleanAttributeInputCallback') {
44+
callback.setInputValue(target.checked);
45+
} else if (attributeType === 'NumberAttributeInputCallback') {
46+
callback.setInputValue(Number(target.value));
47+
} else {
48+
callback.setInputValue(target.value);
49+
}
50+
});
51+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { ChoiceCallback } from '@forgerock/journey-client/types';
8+
9+
export default function choiceComponent(
10+
journeyEl: HTMLDivElement,
11+
callback: ChoiceCallback,
12+
idx: number,
13+
) {
14+
const collectorKey = callback?.payload?.input?.[0].name || `collector-${idx}`;
15+
const label = document.createElement('label');
16+
const select = document.createElement('select');
17+
18+
label.htmlFor = collectorKey;
19+
label.innerText = callback.getPrompt();
20+
select.id = collectorKey;
21+
select.name = collectorKey;
22+
23+
// Add choices as options
24+
const choices = callback.getChoices();
25+
const defaultChoice = callback.getDefaultChoice();
26+
27+
choices.forEach((choice, index) => {
28+
const option = document.createElement('option');
29+
option.value = String(index);
30+
option.text = choice;
31+
option.selected = index === defaultChoice;
32+
select.appendChild(option);
33+
});
34+
35+
journeyEl?.appendChild(label);
36+
journeyEl?.appendChild(select);
37+
38+
journeyEl?.querySelector(`#${collectorKey}`)?.addEventListener('change', (event) => {
39+
const selectedIndex = Number((event.target as HTMLSelectElement).value);
40+
callback.setChoiceIndex(selectedIndex);
41+
});
42+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import type { ConfirmationCallback } from '@forgerock/journey-client/types';
8+
9+
export default function confirmationComponent(
10+
journeyEl: HTMLDivElement,
11+
callback: ConfirmationCallback,
12+
idx: number,
13+
) {
14+
const collectorKey = callback?.payload?.input?.[0].name || `collector-${idx}`;
15+
const label = document.createElement('label');
16+
const container = document.createElement('div');
17+
18+
label.innerText = callback.getPrompt();
19+
container.id = collectorKey;
20+
21+
// Get options and default option
22+
const options = callback.getOptions();
23+
const defaultOption = callback.getDefaultOption();
24+
25+
// Create radio buttons for each option
26+
options.forEach((option: string, index: number) => {
27+
const radioContainer = document.createElement('div');
28+
const radio = document.createElement('input');
29+
const radioLabel = document.createElement('label');
30+
31+
radio.type = 'radio';
32+
radio.id = `${collectorKey}-${index}`;
33+
radio.name = collectorKey;
34+
radio.value = String(index);
35+
radio.checked = index === defaultOption;
36+
37+
radioLabel.htmlFor = `${collectorKey}-${index}`;
38+
radioLabel.innerText = option;
39+
40+
radioContainer.appendChild(radio);
41+
radioContainer.appendChild(radioLabel);
42+
container.appendChild(radioContainer);
43+
});
44+
45+
journeyEl?.appendChild(label);
46+
journeyEl?.appendChild(container);
47+
48+
// Add event listener for radio button changes
49+
journeyEl?.querySelectorAll(`input[name="${collectorKey}"]`)?.forEach((radio) => {
50+
radio.addEventListener('change', (event) => {
51+
if ((event.target as HTMLInputElement).checked) {
52+
const selectedIndex = Number((event.target as HTMLInputElement).value);
53+
callback.setOptionIndex(selectedIndex);
54+
}
55+
});
56+
});
57+
}

0 commit comments

Comments
 (0)