Commit 2ddeaea2 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '229100-add-new-createflash-function-that-takes-an-object' into 'master'

Add new `createFlash` function that takes an object

Closes #229100

See merge request gitlab-org/gitlab!38373
parents 8ad1afad 6dec4c72
import * as Sentry from '@sentry/browser';
import { escape } from 'lodash';
import { spriteIcon } from './lib/utils/common_utils';
......@@ -109,8 +110,65 @@ const createFlash = function createFlash(
return flashContainer;
};
/*
* Flash banner supports different types of Flash configurations
* along with ability to provide actionConfig which can be used to show
* additional action or link on banner next to message
*
* @param {Object} options Options to control the flash message
* @param {String} options.message Flash message text
* @param {String} options.type Type of Flash, it can be `notice`, `success`, `warning` or `alert` (default)
* @param {Object} options.parent Reference to parent element under which Flash needs to appear
* @param {Object} options.actonConfig Map of config to show action on banner
* @param {String} href URL to which action config should point to (default: '#')
* @param {String} title Title of action
* @param {Function} clickHandler Method to call when action is clicked on
* @param {Boolean} options.fadeTransition Boolean to determine whether to fade the alert out
* @param {Boolean} options.captureError Boolean to determine whether to send error to sentry
* @param {Object} options.error Error to be captured in sentry
*/
const newCreateFlash = function newCreateFlash({
message,
type = FLASH_TYPES.ALERT,
parent = document,
actionConfig = null,
fadeTransition = true,
addBodyClass = false,
captureError = false,
error = null,
}) {
const flashContainer = parent.querySelector('.flash-container');
if (!flashContainer) return null;
flashContainer.innerHTML = createFlashEl(message, type);
const flashEl = flashContainer.querySelector(`.flash-${type}`);
if (actionConfig) {
flashEl.insertAdjacentHTML('beforeend', createAction(actionConfig));
if (actionConfig.clickHandler) {
flashEl
.querySelector('.flash-action')
.addEventListener('click', e => actionConfig.clickHandler(e));
}
}
removeFlashClickListener(flashEl, fadeTransition);
flashContainer.classList.add('gl-display-block');
if (addBodyClass) document.body.classList.add('flash-shown');
if (captureError && error) Sentry.captureException(error);
return flashContainer;
};
export {
createFlash as default,
newCreateFlash,
createFlashEl,
createAction,
hideFlash,
......
import flash, { createFlashEl, createAction, hideFlash, removeFlashClickListener } from '~/flash';
import flash, {
newCreateFlash,
createFlashEl,
createAction,
hideFlash,
removeFlashClickListener,
} from '~/flash';
describe('Flash', () => {
describe('createFlashEl', () => {
......@@ -205,6 +211,109 @@ describe('Flash', () => {
});
});
describe('newCreateFlash', () => {
const message = 'test';
const type = 'alert';
const parent = document;
const fadeTransition = false;
const addBodyClass = true;
const defaultParams = {
message,
type,
parent,
actionConfig: null,
fadeTransition,
addBodyClass,
};
describe('no flash-container', () => {
it('does not add to the DOM', () => {
const flashEl = newCreateFlash({ message });
expect(flashEl).toBeNull();
expect(document.querySelector('.flash-alert')).toBeNull();
});
});
describe('with flash-container', () => {
beforeEach(() => {
setFixtures(
'<div class="content-wrapper js-content-wrapper"><div class="flash-container"></div></div>',
);
});
afterEach(() => {
document.querySelector('.js-content-wrapper').remove();
});
it('adds flash element into container', () => {
newCreateFlash({ ...defaultParams });
expect(document.querySelector('.flash-alert')).not.toBeNull();
expect(document.body.className).toContain('flash-shown');
});
it('adds flash into specified parent', () => {
newCreateFlash({ ...defaultParams, parent: document.querySelector('.content-wrapper') });
expect(document.querySelector('.content-wrapper .flash-alert')).not.toBeNull();
expect(document.querySelector('.content-wrapper').innerText.trim()).toEqual(message);
});
it('adds container classes when inside content-wrapper', () => {
newCreateFlash(defaultParams);
expect(document.querySelector('.flash-text').className).toBe('flash-text');
expect(document.querySelector('.content-wrapper').innerText.trim()).toEqual(message);
});
it('does not add container when outside of content-wrapper', () => {
document.querySelector('.content-wrapper').className = 'js-content-wrapper';
newCreateFlash(defaultParams);
expect(document.querySelector('.flash-text').className.trim()).toContain('flash-text');
});
it('removes element after clicking', () => {
newCreateFlash({ ...defaultParams });
document.querySelector('.flash-alert .js-close-icon').click();
expect(document.querySelector('.flash-alert')).toBeNull();
expect(document.body.className).not.toContain('flash-shown');
});
describe('with actionConfig', () => {
it('adds action link', () => {
newCreateFlash({
...defaultParams,
actionConfig: {
title: 'test',
},
});
expect(document.querySelector('.flash-action')).not.toBeNull();
});
it('calls actionConfig clickHandler on click', () => {
const actionConfig = {
title: 'test',
clickHandler: jest.fn(),
};
newCreateFlash({ ...defaultParams, actionConfig });
document.querySelector('.flash-action').click();
expect(actionConfig.clickHandler).toHaveBeenCalled();
});
});
});
});
describe('removeFlashClickListener', () => {
beforeEach(() => {
document.body.innerHTML += `
......
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