Skip to content

Commit

Permalink
feat: External Input support in SDK (#798)
Browse files Browse the repository at this point in the history
## Related Issues

Fixes <link_to_github_issue>

## Related PRs

| branch       | PR         |
| ------------ | ---------- |
| service a PR | Link to PR |
| service b PR | Link to PR |

## Description

A few sentences describing the overall goals of the pull request's
commits.

## Must

- [ ] Tests
- [ ] Documentation (if applicable)
  • Loading branch information
tomerlichtash authored Oct 10, 2024
1 parent 9f8f5a6 commit 01cc7fe
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 19 deletions.
4 changes: 2 additions & 2 deletions packages/sdks/web-component/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ module.exports = {
collectCoverageFrom: ['src/lib/**/*.ts'],
coverageThreshold: {
global: {
branches: 81,
functions: 87,
branches: 80,
functions: 86,
lines: 89,
statements: 89,
},
Expand Down
50 changes: 43 additions & 7 deletions packages/sdks/web-component/src/lib/descope-wc/DescopeWc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
leadingDebounce,
handleReportValidityOnBlur,
getUserLocale,
clearPreviousExternalInputs,
} from '../helpers';
import { calculateConditions, calculateCondition } from '../helpers/conditions';
import { getLastAuth, setLastAuth } from '../helpers/lastAuth';
Expand Down Expand Up @@ -842,19 +843,23 @@ class DescopeWc extends BaseDescopeWc {

this.rootElement.replaceChildren(clone);

// If before html url was empty, we deduce its the first time a screen is shown
const isFirstScreen = !prevState.htmlUrl;

// we need to wait for all components to render before we can set its value
setTimeout(() => {
updateScreenFromScreenState(this.rootElement, screenState);
});
this.#updateExternalInputs();

// If before html url was empty, we deduce its the first time a screen is shown
const isFirstScreen = !prevState.htmlUrl;
handleAutoFocus(this.rootElement, this.autoFocus, isFirstScreen);

handleAutoFocus(this.rootElement, this.autoFocus, isFirstScreen);
if (this.validateOnBlur) {
handleReportValidityOnBlur(this.rootElement);
}

if (this.validateOnBlur) {
handleReportValidityOnBlur(this.rootElement);
}
// we need to wait for all components to render before we can set its value
updateScreenFromScreenState(this.rootElement, screenState);
});

this.#hydrate(next);
if (isFirstScreen) {
Expand Down Expand Up @@ -974,6 +979,37 @@ class DescopeWc extends BaseDescopeWc {
}
}

#updateExternalInputs() {
// we need to clear external inputs that were created previously, so each screen has only
// the slotted inputs it needs
clearPreviousExternalInputs();

const eles = this.rootElement.querySelectorAll('[external-input="true"]');
eles.forEach((ele) => this.#handleExternalInputs(ele));
}

#handleExternalInputs(ele: Element) {
if (!ele) {
return;
}

const origInputs = ele.querySelectorAll('input');

origInputs.forEach((inp) => {
const targetSlot = inp.getAttribute('slot');
const id = `input-${ele.id}-${targetSlot}`;

const slot = document.createElement('slot');
slot.setAttribute('name', id);
slot.setAttribute('slot', targetSlot);

ele.appendChild(slot);

inp.setAttribute('slot', id);
this.appendChild(inp);
});
}

// we are wrapping this function with a leading debounce,
// to prevent a scenario where we are calling it multiple times
// this can caused by focusing on a button and pressing enter
Expand Down
6 changes: 6 additions & 0 deletions packages/sdks/web-component/src/lib/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,9 @@ export function getUserLocale(locale: string): Locale {

return { locale: nl.toLowerCase(), fallback: nl.toLowerCase() };
}

export const clearPreviousExternalInputs = () => {
document
.querySelectorAll('[data-hidden-input="true"]')
.forEach((ele) => ele.remove());
};
61 changes: 51 additions & 10 deletions packages/sdks/web-component/test/descope-wc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@ describe('web-component', () => {
await waitFor(() => screen.getByShadowText('It works!'), {
timeout: WAIT_TIMEOUT,
});
expect(autoFocusSpy).toBeCalledWith(expect.any(HTMLElement), true, true);

await waitFor(() =>
expect(autoFocusSpy).toBeCalledWith(expect.any(HTMLElement), true, true),
);
});

it('Auto focus should not happen when auto-focus is false', async () => {
Expand All @@ -347,7 +350,10 @@ describe('web-component', () => {
await waitFor(() => screen.getByShadowText('It works!'), {
timeout: WAIT_TIMEOUT,
});
expect(autoFocusSpy).toBeCalledWith(expect.any(HTMLElement), false, true);

await waitFor(() =>
expect(autoFocusSpy).toBeCalledWith(expect.any(HTMLElement), false, true),
);
});

it('Auto focus should not happen when auto-focus is `skipFirstScreen`', async () => {
Expand All @@ -362,10 +368,13 @@ describe('web-component', () => {
await waitFor(() => screen.getByShadowText('It works!'), {
timeout: WAIT_TIMEOUT,
});
expect(autoFocusSpy).toBeCalledWith(
expect.any(HTMLElement),
'skipFirstScreen',
true,

await waitFor(() =>
expect(autoFocusSpy).toBeCalledWith(
expect.any(HTMLElement),
'skipFirstScreen',
true,
),
);
autoFocusSpy.mockClear();

Expand Down Expand Up @@ -3986,13 +3995,13 @@ describe('web-component', () => {

(<HTMLInputElement>emailInput).reportValidity = jest.fn();

fireEvent.blur(emailInput);
await waitFor(() => {
fireEvent.blur(emailInput);

await waitFor(() =>
expect(
(<HTMLInputElement>emailInput).reportValidity,
).toHaveBeenCalledTimes(1),
);
).toHaveBeenCalledTimes(1);
});
});

it('should not call report validity on blur by default', async () => {
Expand Down Expand Up @@ -4214,4 +4223,36 @@ describe('web-component', () => {
expect(screen.getByShadowText('ho!')).toHaveAttribute('href', 'john'),
);
});

it('should handle external input components', async () => {
startMock.mockReturnValue(generateSdkResponse());
const clearPreviousExtInputsSpy = jest.spyOn(
helpers,
'clearPreviousExternalInputs',
);

pageContent =
'<input id="should-be-removed" data-hidden-input="true"/><div external-input="true" id="email"><input slot="test-slot" type="email"/></div><span>It works!</span>';
document.body.innerHTML = `<h1>Custom element test</h1> <descope-wc flow-id="otpSignInEmail" project-id="1"></descope-wc>`;

await waitFor(() => screen.getByShadowText('It works!'), {
timeout: WAIT_TIMEOUT,
});

// previous external input cleared
await waitFor(() =>
expect(clearPreviousExtInputsSpy).toHaveBeenCalledTimes(1),
);

const rootEle = document.getElementsByTagName('descope-wc')[0];

// new external input created
await waitFor(
() =>
expect(
rootEle.querySelector('input[slot="input-email-test-slot"]'),
).toHaveAttribute('type', 'email'),
{ timeout: WAIT_TIMEOUT },
);
});
});

0 comments on commit 01cc7fe

Please sign in to comment.