Commit ead47d77 authored by Chad Woolley's avatar Chad Woolley Committed by Paul Slaughter

Add new initRecaptchaScript memoized function

- Add a memoized `initRecaptchaScript` function, which
  attaches the reCAPTCHA script to the document head.
- It returns a Promise which resolves when the reCAPTCHA
  script has finished initialization.
parent 578ab1bb
// NOTE: This module will be used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52044
import { memoize } from 'lodash';
export const RECAPTCHA_API_URL_PREFIX = 'https://www.google.com/recaptcha/api.js';
/**
* The name which will be used for the reCAPTCHA script's onload callback
*/
export const RECAPTCHA_ONLOAD_CALLBACK_NAME = 'recaptchaOnloadCallback';
/**
* Adds the Google reCAPTCHA script tag to the head of the document, and
* returns a promise of the grecaptcha object
* (https://developers.google.com/recaptcha/docs/display#js_api).
*
* It is memoized, so there will only be one instance of the script tag ever
* added to the document.
*
* See the reCAPTCHA documentation for more details:
*
* https://developers.google.com/recaptcha/docs/display#explicit_render
*
*/
export const initRecaptchaScript = memoize(() => {
/**
* Appends the the reCAPTCHA script tag to the head of document
*/
const appendRecaptchaScript = () => {
const script = document.createElement('script');
script.src = `${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit`;
script.classList.add('js-recaptcha-script');
document.head.appendChild(script);
};
/**
* Returns a Promise which is fulfilled after the reCAPTCHA script is loaded
*/
return new Promise((resolve) => {
window[RECAPTCHA_ONLOAD_CALLBACK_NAME] = resolve;
appendRecaptchaScript();
});
});
/**
* Clears the cached memoization of the default manager.
*
* This is needed for determinism in tests.
*/
export const clearMemoizeCache = () => {
initRecaptchaScript.cache.clear();
};
import {
RECAPTCHA_API_URL_PREFIX,
RECAPTCHA_ONLOAD_CALLBACK_NAME,
clearMemoizeCache,
initRecaptchaScript,
} from '~/captcha/init_recaptcha_script';
describe('initRecaptchaScript', () => {
afterEach(() => {
// NOTE: The DOM is guaranteed to be clean at the start of a new test file, but it isn't cleaned
// between examples within a file, so we need to clean it after each one. See more context here:
// - https://github.com/facebook/jest/issues/1224
// - https://stackoverflow.com/questions/42805128/does-jest-reset-the-jsdom-document-after-every-suite-or-test
//
// Also note as mentioned in https://github.com/facebook/jest/issues/1224#issuecomment-444586798
// that properties of `window` are NOT cleared between test files. So, we are also
// explicitly unsetting it.
document.head.innerHTML = '';
window[RECAPTCHA_ONLOAD_CALLBACK_NAME] = undefined;
clearMemoizeCache();
});
const triggerScriptOnload = (...args) => window[RECAPTCHA_ONLOAD_CALLBACK_NAME](...args);
describe('when called', () => {
let result;
beforeEach(() => {
result = initRecaptchaScript();
});
it('adds script to head', () => {
expect(document.head).toMatchInlineSnapshot(`
<head>
<script
class="js-recaptcha-script"
src="${RECAPTCHA_API_URL_PREFIX}?onload=${RECAPTCHA_ONLOAD_CALLBACK_NAME}&render=explicit"
/>
</head>
`);
});
it('is memoized', () => {
expect(initRecaptchaScript()).toBe(result);
expect(document.head.querySelectorAll('script').length).toBe(1);
});
it('when onload is triggered, resolves promise', async () => {
const instance = {};
triggerScriptOnload(instance);
await expect(result).resolves.toBe(instance);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment