summaryrefslogtreecommitdiff
path: root/spec/frontend/captcha/captcha_modal_spec.js
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 10:34:06 +0000
commit859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch)
treed7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /spec/frontend/captcha/captcha_modal_spec.js
parent446d496a6d000c73a304be52587cd9bbc7493136 (diff)
downloadgitlab-ce-13.9.0-rc42.tar.gz
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'spec/frontend/captcha/captcha_modal_spec.js')
-rw-r--r--spec/frontend/captcha/captcha_modal_spec.js171
1 files changed, 171 insertions, 0 deletions
diff --git a/spec/frontend/captcha/captcha_modal_spec.js b/spec/frontend/captcha/captcha_modal_spec.js
new file mode 100644
index 00000000000..b8448f9ff0a
--- /dev/null
+++ b/spec/frontend/captcha/captcha_modal_spec.js
@@ -0,0 +1,171 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { stubComponent } from 'helpers/stub_component';
+import CaptchaModal from '~/captcha/captcha_modal.vue';
+import { initRecaptchaScript } from '~/captcha/init_recaptcha_script';
+
+jest.mock('~/captcha/init_recaptcha_script');
+
+describe('Captcha Modal', () => {
+ let wrapper;
+ let modal;
+ let grecaptcha;
+
+ const captchaSiteKey = 'abc123';
+
+ function createComponent({ props = {} } = {}) {
+ wrapper = shallowMount(CaptchaModal, {
+ propsData: {
+ captchaSiteKey,
+ ...props,
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal),
+ },
+ });
+ }
+
+ beforeEach(() => {
+ grecaptcha = {
+ render: jest.fn(),
+ };
+
+ initRecaptchaScript.mockResolvedValue(grecaptcha);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findGlModal = () => {
+ const glModal = wrapper.find(GlModal);
+
+ jest.spyOn(glModal.vm, 'show').mockImplementation(() => glModal.vm.$emit('shown'));
+ jest
+ .spyOn(glModal.vm, 'hide')
+ .mockImplementation(() => glModal.vm.$emit('hide', { trigger: '' }));
+
+ return glModal;
+ };
+
+ const showModal = () => {
+ wrapper.setProps({ needsCaptchaResponse: true });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ modal = findGlModal();
+ });
+
+ describe('rendering', () => {
+ it('renders', () => {
+ expect(modal.exists()).toBe(true);
+ });
+
+ it('assigns the modal a unique ID', () => {
+ const firstInstanceModalId = modal.props('modalId');
+ createComponent();
+ const secondInstanceModalId = findGlModal().props('modalId');
+ expect(firstInstanceModalId).not.toEqual(secondInstanceModalId);
+ });
+ });
+
+ describe('functionality', () => {
+ describe('when modal is shown', () => {
+ describe('when initRecaptchaScript promise resolves successfully', () => {
+ beforeEach(async () => {
+ showModal();
+
+ await nextTick();
+ });
+
+ it('shows modal', async () => {
+ expect(findGlModal().vm.show).toHaveBeenCalled();
+ });
+
+ it('renders window.grecaptcha', () => {
+ expect(grecaptcha.render).toHaveBeenCalledWith(wrapper.vm.$refs.captcha, {
+ sitekey: captchaSiteKey,
+ callback: expect.any(Function),
+ });
+ });
+
+ describe('then the user solves the captcha', () => {
+ const captchaResponse = 'a captcha response';
+
+ beforeEach(() => {
+ // simulate the grecaptcha library invoking the callback
+ const { callback } = grecaptcha.render.mock.calls[0][1];
+ callback(captchaResponse);
+ });
+
+ it('emits receivedCaptchaResponse exactly once with the captcha response', () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual([[captchaResponse]]);
+ });
+
+ it('hides modal with null trigger', async () => {
+ // Assert that hide is called with zero args, so that we don't trigger the logic
+ // for hiding the modal via cancel, esc, headerclose, etc, without a captcha response
+ expect(modal.vm.hide).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('then the user hides the modal without solving the captcha', () => {
+ // Even though we don't explicitly check for these trigger values, these are the
+ // currently supported ones which can be emitted.
+ // See https://bootstrap-vue.org/docs/components/modal#prevent-closing
+ describe.each`
+ trigger | expected
+ ${'cancel'} | ${[[null]]}
+ ${'esc'} | ${[[null]]}
+ ${'backdrop'} | ${[[null]]}
+ ${'headerclose'} | ${[[null]]}
+ `('using the $trigger trigger', ({ trigger, expected }) => {
+ beforeEach(() => {
+ const bvModalEvent = {
+ trigger,
+ };
+ modal.vm.$emit('hide', bvModalEvent);
+ });
+
+ it(`emits receivedCaptchaResponse with ${JSON.stringify(expected)}`, () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual(expected);
+ });
+ });
+ });
+ });
+
+ describe('when initRecaptchaScript promise rejects', () => {
+ const fakeError = {};
+
+ beforeEach(() => {
+ initRecaptchaScript.mockImplementation(() => Promise.reject(fakeError));
+
+ jest.spyOn(console, 'error').mockImplementation();
+
+ showModal();
+ });
+
+ it('emits receivedCaptchaResponse exactly once with null', () => {
+ expect(wrapper.emitted('receivedCaptchaResponse')).toEqual([[null]]);
+ });
+
+ it('hides modal with null trigger', async () => {
+ // Assert that hide is called with zero args, so that we don't trigger the logic
+ // for hiding the modal via cancel, esc, headerclose, etc, without a captcha response
+ expect(modal.vm.hide).toHaveBeenCalledWith();
+ });
+
+ it('calls console.error with a message and the exception', () => {
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledWith(
+ expect.stringMatching(/exception.*captcha/),
+ fakeError,
+ );
+ });
+ });
+ });
+ });
+});