298 lines
8.7 KiB
JavaScript
298 lines
8.7 KiB
JavaScript
import waitForPromises from 'helpers/wait_for_promises';
|
|
|
|
import * as UserApi from '~/api/user_api';
|
|
import {
|
|
createUserCountsManager,
|
|
userCounts,
|
|
destroyUserCountsManager,
|
|
setGlobalTodoCount,
|
|
} from '~/super_sidebar/user_counts_manager';
|
|
import { fetchUserCounts } from '~/super_sidebar/user_counts_fetch';
|
|
|
|
jest.mock('~/api');
|
|
|
|
const USER_ID = 123;
|
|
const userCountDefaults = {
|
|
todos: 1,
|
|
assigned_issues: 2,
|
|
assigned_merge_requests: 3,
|
|
review_requested_merge_requests: 4,
|
|
};
|
|
|
|
const userCountUpdate = {
|
|
todos: 123,
|
|
assigned_issues: 456,
|
|
assigned_merge_requests: 789,
|
|
review_requested_merge_requests: 101112,
|
|
};
|
|
|
|
describe('User Count Manager', () => {
|
|
let channelMock;
|
|
let newBroadcastChannelMock;
|
|
|
|
beforeEach(() => {
|
|
jest.spyOn(document, 'removeEventListener');
|
|
jest.spyOn(document, 'addEventListener');
|
|
|
|
global.gon.current_user_id = USER_ID;
|
|
|
|
channelMock = {
|
|
postMessage: jest.fn(),
|
|
close: jest.fn(),
|
|
};
|
|
newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
|
|
|
|
Object.assign(userCounts, userCountDefaults, { last_update: 0 });
|
|
|
|
global.BroadcastChannel = newBroadcastChannelMock;
|
|
});
|
|
|
|
describe('userCounts', () => {
|
|
it('returns total_merge_requests', () => {
|
|
expect(userCounts.total_merge_requests).toEqual(7);
|
|
|
|
Object.assign(userCounts, userCountUpdate, { last_update: 0 });
|
|
|
|
expect(userCounts.total_merge_requests).toEqual(101901);
|
|
});
|
|
});
|
|
|
|
describe('createUserCountsManager', () => {
|
|
beforeEach(() => {
|
|
createUserCountsManager();
|
|
});
|
|
|
|
it('creates BroadcastChannel which updates counts on message received', () => {
|
|
expect(newBroadcastChannelMock).toHaveBeenCalledWith(`user_counts_${USER_ID}`);
|
|
});
|
|
|
|
it('closes BroadCastchannel if called while already open', () => {
|
|
expect(channelMock.close).not.toHaveBeenCalled();
|
|
|
|
createUserCountsManager();
|
|
|
|
expect(channelMock.close).toHaveBeenCalled();
|
|
});
|
|
|
|
describe('BroadcastChannel onmessage handler', () => {
|
|
it('updates counts on message received', () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
|
|
channelMock.onmessage({ data: { ...userCountUpdate, last_update: Date.now() } });
|
|
|
|
expect(userCounts).toMatchObject(userCountUpdate);
|
|
});
|
|
|
|
it('ignores updates with older data', () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
userCounts.last_update = Date.now();
|
|
|
|
channelMock.onmessage({
|
|
data: { ...userCountUpdate, last_update: userCounts.last_update - 1000 },
|
|
});
|
|
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
});
|
|
|
|
it('ignores unknown fields', () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
|
|
channelMock.onmessage({ data: { ...userCountUpdate, i_am_unknown: 5 } });
|
|
|
|
expect(userCounts).toMatchObject(userCountUpdate);
|
|
expect(userCounts.i_am_unknown).toBeUndefined();
|
|
});
|
|
|
|
it('does not update total_merge_requests', () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
expect(userCounts.total_merge_requests).toEqual(7);
|
|
|
|
channelMock.onmessage({ data: { ...userCountUpdate, total_merge_requests: 22 } });
|
|
|
|
expect(userCounts).toMatchObject(userCountUpdate);
|
|
expect(userCounts.total_merge_requests).toEqual(101901);
|
|
});
|
|
});
|
|
|
|
it('broadcasts user counts during initialization', () => {
|
|
expect(channelMock.postMessage).toHaveBeenCalledWith(
|
|
expect.objectContaining(userCountDefaults),
|
|
);
|
|
});
|
|
|
|
it('setups event listener without leaking them', () => {
|
|
expect(document.removeEventListener).toHaveBeenCalledWith(
|
|
'userCounts:fetch',
|
|
expect.any(Function),
|
|
);
|
|
expect(document.addEventListener).toHaveBeenCalledWith(
|
|
'userCounts:fetch',
|
|
expect.any(Function),
|
|
);
|
|
expect(document.removeEventListener).toHaveBeenCalledWith(
|
|
'todo:toggle',
|
|
expect.any(Function),
|
|
);
|
|
expect(document.addEventListener).toHaveBeenCalledWith('todo:toggle', expect.any(Function));
|
|
});
|
|
});
|
|
|
|
describe('Event listener userCounts:fetch', () => {
|
|
beforeEach(() => {
|
|
jest.spyOn(UserApi, 'getUserCounts').mockResolvedValue({
|
|
data: { ...userCountUpdate, merge_requests: 'FOO' },
|
|
});
|
|
createUserCountsManager();
|
|
});
|
|
|
|
describe('manually created event', () => {
|
|
it('fetches counts from API, stores and rebroadcasts them', async () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
|
|
document.dispatchEvent(new CustomEvent('userCounts:fetch'));
|
|
await waitForPromises();
|
|
|
|
expect(UserApi.getUserCounts).toHaveBeenCalled();
|
|
expect(userCounts).toMatchObject(userCountUpdate);
|
|
expect(channelMock.postMessage).toHaveBeenLastCalledWith(userCounts);
|
|
});
|
|
});
|
|
|
|
describe('fetchUserCounts helper', () => {
|
|
it('fetches counts from API, stores and rebroadcasts them', async () => {
|
|
expect(userCounts).toMatchObject(userCountDefaults);
|
|
|
|
fetchUserCounts();
|
|
await waitForPromises();
|
|
|
|
expect(UserApi.getUserCounts).toHaveBeenCalled();
|
|
expect(userCounts).toMatchObject(userCountUpdate);
|
|
expect(channelMock.postMessage).toHaveBeenLastCalledWith(userCounts);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Event listener todo:toggle', () => {
|
|
beforeEach(() => {
|
|
createUserCountsManager();
|
|
userCounts.todos = 10;
|
|
});
|
|
|
|
describe('with total count', () => {
|
|
it.each([
|
|
{ count: 123, expected: 123 },
|
|
{ count: -500, expected: 0 },
|
|
{ count: 0, expected: 0 },
|
|
{ count: NaN, expected: 10 },
|
|
{ count: '99+', expected: 10 },
|
|
])(`with count: $count results in $expected`, ({ count, expected }) => {
|
|
expect(userCounts.todos).toBe(10);
|
|
|
|
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { count } }));
|
|
|
|
expect(userCounts.todos).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('with diff on count', () => {
|
|
it.each([
|
|
{ delta: 5, expected: 15 },
|
|
{ delta: -5, expected: 5 },
|
|
{ delta: 0, expected: 10 },
|
|
{ delta: -100, expected: 0 },
|
|
{ delta: NaN, expected: 10 },
|
|
{ delta: '99+', expected: 10 },
|
|
])(`with count: $diff results in $expected`, ({ delta, expected }) => {
|
|
expect(userCounts.todos).toBe(10);
|
|
|
|
document.dispatchEvent(new CustomEvent('todo:toggle', { detail: { delta } }));
|
|
|
|
expect(userCounts.todos).toBe(expected);
|
|
});
|
|
});
|
|
|
|
it('updates count over delta if both are defined', () => {
|
|
expect(userCounts.todos).toBe(10);
|
|
|
|
const detail = {
|
|
count: 20,
|
|
delta: -5,
|
|
};
|
|
|
|
document.dispatchEvent(new CustomEvent('todo:toggle', { detail }));
|
|
|
|
expect(userCounts.todos).toBe(detail.count);
|
|
});
|
|
});
|
|
|
|
describe('destroyUserCountsManager', () => {
|
|
it('unregisters event handler', () => {
|
|
expect(document.removeEventListener).not.toHaveBeenCalledWith();
|
|
|
|
destroyUserCountsManager();
|
|
|
|
expect(document.removeEventListener).toHaveBeenCalledWith(
|
|
'userCounts:fetch',
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
describe('when BroadcastChannel is not opened', () => {
|
|
it('does nothing', () => {
|
|
destroyUserCountsManager();
|
|
expect(channelMock.close).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('when BroadcastChannel is opened', () => {
|
|
beforeEach(() => {
|
|
createUserCountsManager();
|
|
});
|
|
|
|
it('closes BroadcastChannel', () => {
|
|
expect(channelMock.close).not.toHaveBeenCalled();
|
|
|
|
destroyUserCountsManager();
|
|
|
|
expect(channelMock.close).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('setGlobalTodoCount', () => {
|
|
beforeEach(() => {
|
|
createUserCountsManager();
|
|
channelMock.postMessage.mockClear();
|
|
});
|
|
|
|
describe('when called with invalid values', () => {
|
|
it.each([undefined, null, '435', -12, Number.MAX_SAFE_INTEGER + 1])(
|
|
`does nothing for %s`,
|
|
(value) => {
|
|
expect(userCounts.todos).toBe(userCountDefaults.todos);
|
|
|
|
setGlobalTodoCount(value);
|
|
|
|
expect(userCounts.todos).toBe(userCountDefaults.todos);
|
|
expect(userCounts.todos).not.toBe(value);
|
|
expect(channelMock.postMessage).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('when called with valid values', () => {
|
|
it.each([0, 3, 12023])(`does update the todos value and broadcast for %s`, (value) => {
|
|
expect(userCounts.todos).not.toBe(value);
|
|
|
|
setGlobalTodoCount(value);
|
|
|
|
expect(userCounts.todos).toBe(value);
|
|
expect(channelMock.postMessage).toHaveBeenCalledWith({
|
|
last_update: Date.now(),
|
|
todos: value,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|