424 lines
13 KiB
JavaScript
424 lines
13 KiB
JavaScript
import { mount } from '@vue/test-utils';
|
|
import { nextTick } from 'vue';
|
|
import DesignOverlay from '~/design_management/components/design_overlay.vue';
|
|
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management/constants';
|
|
import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
|
|
import notes from '../mock_data/notes';
|
|
|
|
const mutate = jest.fn(() => Promise.resolve());
|
|
|
|
describe('Design overlay component', () => {
|
|
let wrapper;
|
|
|
|
const mockDimensions = { width: 100, height: 100 };
|
|
|
|
const findAllNotes = () => wrapper.findAll('.js-image-badge');
|
|
const findCommentBadge = () => wrapper.find('.comment-indicator');
|
|
const findBadgeAtIndex = (noteIndex) => findAllNotes().at(noteIndex);
|
|
const findFirstBadge = () => findBadgeAtIndex(0);
|
|
const findSecondBadge = () => findBadgeAtIndex(1);
|
|
|
|
const clickAndDragBadge = async (elem, fromPoint, toPoint) => {
|
|
elem.trigger('mousedown', { clientX: fromPoint.x, clientY: fromPoint.y });
|
|
await nextTick();
|
|
elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
|
|
await nextTick();
|
|
};
|
|
|
|
function createComponent(props = {}, data = {}) {
|
|
wrapper = mount(DesignOverlay, {
|
|
propsData: {
|
|
dimensions: mockDimensions,
|
|
position: {
|
|
top: '0',
|
|
left: '0',
|
|
},
|
|
resolvedDiscussionsExpanded: false,
|
|
...props,
|
|
},
|
|
data() {
|
|
return {
|
|
activeDiscussion: {
|
|
id: null,
|
|
source: null,
|
|
},
|
|
...data,
|
|
};
|
|
},
|
|
mocks: {
|
|
$apollo: {
|
|
mutate,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
it('should have correct inline style', () => {
|
|
createComponent();
|
|
|
|
expect(wrapper.attributes().style).toBe('width: 100px; height: 100px; top: 0px; left: 0px;');
|
|
});
|
|
|
|
it('should emit `openCommentForm` when clicking on overlay', async () => {
|
|
createComponent();
|
|
const newCoordinates = {
|
|
x: 10,
|
|
y: 10,
|
|
};
|
|
|
|
wrapper
|
|
.find('[data-qa-selector="design_image_button"]')
|
|
.trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y });
|
|
await nextTick();
|
|
expect(wrapper.emitted('openCommentForm')).toEqual([
|
|
[{ x: newCoordinates.x, y: newCoordinates.y }],
|
|
]);
|
|
});
|
|
|
|
describe('with notes', () => {
|
|
it('should render only the first note', () => {
|
|
createComponent({
|
|
notes,
|
|
});
|
|
expect(findAllNotes()).toHaveLength(1);
|
|
});
|
|
|
|
describe('with resolved discussions toggle expanded', () => {
|
|
beforeEach(() => {
|
|
createComponent({
|
|
notes,
|
|
resolvedDiscussionsExpanded: true,
|
|
});
|
|
});
|
|
|
|
it('should render all notes', () => {
|
|
expect(findAllNotes()).toHaveLength(notes.length);
|
|
});
|
|
|
|
it('should have set the correct position for each note badge', () => {
|
|
expect(findFirstBadge().attributes().style).toBe('left: 10px; top: 15px;');
|
|
expect(findSecondBadge().attributes().style).toBe('left: 50px; top: 50px;');
|
|
});
|
|
|
|
it('should apply resolved class to the resolved note pin', () => {
|
|
expect(findSecondBadge().classes()).toContain('resolved');
|
|
});
|
|
|
|
describe('when no discussion is active', () => {
|
|
it('should not apply inactive class to any pins', () => {
|
|
expect(
|
|
findAllNotes(0).wrappers.every((designNote) => designNote.classes('gl-bg-blue-50')),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('when a discussion is active', () => {
|
|
it.each([notes[0].discussion.notes.nodes[1], notes[0].discussion.notes.nodes[0]])(
|
|
'should not apply inactive class to the pin for the active discussion',
|
|
async (note) => {
|
|
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
wrapper.setData({
|
|
activeDiscussion: {
|
|
id: note.id,
|
|
source: 'discussion',
|
|
},
|
|
});
|
|
|
|
await nextTick();
|
|
expect(findBadgeAtIndex(0).classes()).not.toContain('inactive');
|
|
},
|
|
);
|
|
|
|
it('should apply inactive class to all pins besides the active one', async () => {
|
|
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
wrapper.setData({
|
|
activeDiscussion: {
|
|
id: notes[0].id,
|
|
source: 'discussion',
|
|
},
|
|
});
|
|
|
|
await nextTick();
|
|
expect(findSecondBadge().classes()).toContain('inactive');
|
|
expect(findFirstBadge().classes()).not.toContain('inactive');
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should recalculate badges positions on window resize', async () => {
|
|
createComponent({
|
|
notes,
|
|
dimensions: {
|
|
width: 400,
|
|
height: 400,
|
|
},
|
|
});
|
|
|
|
expect(findFirstBadge().attributes().style).toBe('left: 40px; top: 60px;');
|
|
|
|
wrapper.setProps({
|
|
dimensions: {
|
|
width: 200,
|
|
height: 200,
|
|
},
|
|
});
|
|
|
|
await nextTick();
|
|
expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
|
|
});
|
|
|
|
it('should call an update active discussion mutation when clicking a note without moving it', async () => {
|
|
const note = notes[0];
|
|
const { position } = note;
|
|
const mutationVariables = {
|
|
mutation: updateActiveDiscussion,
|
|
variables: {
|
|
id: note.id,
|
|
source: ACTIVE_DISCUSSION_SOURCE_TYPES.pin,
|
|
},
|
|
};
|
|
|
|
findFirstBadge().trigger('mousedown', { clientX: position.x, clientY: position.y });
|
|
|
|
await nextTick();
|
|
findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
|
|
expect(mutate).toHaveBeenCalledWith(mutationVariables);
|
|
});
|
|
});
|
|
|
|
describe('when moving notes', () => {
|
|
it('should update badge style when note is being moved', async () => {
|
|
createComponent({
|
|
notes,
|
|
});
|
|
|
|
const { position } = notes[0];
|
|
|
|
await clickAndDragBadge(findFirstBadge(), { x: position.x, y: position.y }, { x: 20, y: 20 });
|
|
expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px;');
|
|
});
|
|
|
|
it('should emit `moveNote` event when note-moving action ends', async () => {
|
|
createComponent({ notes });
|
|
const note = notes[0];
|
|
const { position } = note;
|
|
const newCoordinates = { x: 20, y: 20 };
|
|
|
|
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
wrapper.setData({
|
|
movingNoteNewPosition: {
|
|
...position,
|
|
...newCoordinates,
|
|
},
|
|
movingNoteStartPosition: {
|
|
noteId: notes[0].id,
|
|
discussionId: notes[0].discussion.id,
|
|
...position,
|
|
},
|
|
});
|
|
|
|
const badge = findFirstBadge();
|
|
await clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates);
|
|
badge.trigger('mouseup');
|
|
|
|
await nextTick();
|
|
expect(wrapper.emitted('moveNote')).toEqual([
|
|
[
|
|
{
|
|
noteId: notes[0].id,
|
|
discussionId: notes[0].discussion.id,
|
|
coordinates: newCoordinates,
|
|
},
|
|
],
|
|
]);
|
|
});
|
|
|
|
describe('without [repositionNote] permission', () => {
|
|
const mockNoteNotAuthorised = {
|
|
...notes[0],
|
|
userPermissions: {
|
|
repositionNote: false,
|
|
},
|
|
};
|
|
|
|
const mockNoteCoordinates = {
|
|
x: mockNoteNotAuthorised.position.x,
|
|
y: mockNoteNotAuthorised.position.y,
|
|
};
|
|
|
|
it('should be unable to move a note', async () => {
|
|
createComponent({
|
|
dimensions: mockDimensions,
|
|
notes: [mockNoteNotAuthorised],
|
|
});
|
|
|
|
const badge = findAllNotes().at(0);
|
|
await clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 });
|
|
// note position should not change after a click-and-drag attempt
|
|
expect(findFirstBadge().attributes().style).toContain(
|
|
`left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('with a new form', () => {
|
|
it('should render a new comment badge', () => {
|
|
createComponent({
|
|
currentCommentForm: {
|
|
...notes[0].position,
|
|
},
|
|
});
|
|
|
|
expect(findCommentBadge().exists()).toBe(true);
|
|
expect(findCommentBadge().attributes().style).toBe('left: 10px; top: 15px;');
|
|
});
|
|
|
|
describe('when moving the comment badge', () => {
|
|
it('should update badge style to reflect new position', async () => {
|
|
const { position } = notes[0];
|
|
|
|
createComponent({
|
|
currentCommentForm: {
|
|
...position,
|
|
},
|
|
});
|
|
|
|
await clickAndDragBadge(
|
|
findCommentBadge(),
|
|
{ x: position.x, y: position.y },
|
|
{ x: 20, y: 20 },
|
|
);
|
|
expect(findCommentBadge().attributes().style).toBe('left: 20px; top: 20px;');
|
|
});
|
|
|
|
it('should update badge style when note-moving action ends', async () => {
|
|
const { position } = notes[0];
|
|
createComponent({
|
|
currentCommentForm: {
|
|
...position,
|
|
},
|
|
});
|
|
|
|
const commentBadge = findCommentBadge();
|
|
const toPoint = { x: 20, y: 20 };
|
|
|
|
await clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint);
|
|
commentBadge.trigger('mouseup');
|
|
// simulates the currentCommentForm being updated in index.vue component, and
|
|
// propagated back down to this prop
|
|
wrapper.setProps({
|
|
currentCommentForm: { height: position.height, width: position.width, ...toPoint },
|
|
});
|
|
|
|
await nextTick();
|
|
expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
|
|
});
|
|
|
|
it.each`
|
|
element | getElementFunc | event
|
|
${'overlay'} | ${() => wrapper} | ${'mouseleave'}
|
|
${'comment badge'} | ${findCommentBadge} | ${'mouseup'}
|
|
`(
|
|
'should emit `openCommentForm` event when $event fired on $element element',
|
|
async ({ getElementFunc, event }) => {
|
|
createComponent({
|
|
notes,
|
|
currentCommentForm: {
|
|
...notes[0].position,
|
|
},
|
|
});
|
|
|
|
const newCoordinates = { x: 20, y: 20 };
|
|
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
wrapper.setData({
|
|
movingNoteStartPosition: {
|
|
...notes[0].position,
|
|
},
|
|
movingNoteNewPosition: {
|
|
...notes[0].position,
|
|
...newCoordinates,
|
|
},
|
|
});
|
|
|
|
getElementFunc().trigger(event);
|
|
await nextTick();
|
|
expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getMovingNotePositionDelta', () => {
|
|
it('should calculate delta correctly from state', () => {
|
|
createComponent();
|
|
|
|
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
wrapper.setData({
|
|
movingNoteStartPosition: {
|
|
clientX: 10,
|
|
clientY: 20,
|
|
},
|
|
});
|
|
|
|
const mockMouseEvent = {
|
|
clientX: 30,
|
|
clientY: 10,
|
|
};
|
|
|
|
expect(wrapper.vm.getMovingNotePositionDelta(mockMouseEvent)).toEqual({
|
|
deltaX: 20,
|
|
deltaY: -10,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('isPositionInOverlay', () => {
|
|
createComponent({ dimensions: mockDimensions });
|
|
|
|
it.each`
|
|
test | coordinates | expectedResult
|
|
${'within overlay bounds'} | ${{ x: 50, y: 50 }} | ${true}
|
|
${'outside overlay bounds'} | ${{ x: 101, y: 101 }} | ${false}
|
|
`('returns [$expectedResult] when position is $test', ({ coordinates, expectedResult }) => {
|
|
const position = { ...mockDimensions, ...coordinates };
|
|
|
|
expect(wrapper.vm.isPositionInOverlay(position)).toBe(expectedResult);
|
|
});
|
|
});
|
|
|
|
describe('getNoteRelativePosition', () => {
|
|
it('calculates position correctly', () => {
|
|
createComponent({ dimensions: mockDimensions });
|
|
const position = { x: 50, y: 50, width: 200, height: 200 };
|
|
|
|
expect(wrapper.vm.getNoteRelativePosition(position)).toEqual({ left: 25, top: 25 });
|
|
});
|
|
});
|
|
|
|
describe('canMoveNote', () => {
|
|
it.each`
|
|
repositionNotePermission | canMoveNoteResult
|
|
${true} | ${true}
|
|
${false} | ${false}
|
|
${undefined} | ${false}
|
|
`(
|
|
'returns [$canMoveNoteResult] when [repositionNote permission] is [$repositionNotePermission]',
|
|
({ repositionNotePermission, canMoveNoteResult }) => {
|
|
createComponent();
|
|
|
|
const note = {
|
|
userPermissions: {
|
|
repositionNote: repositionNotePermission,
|
|
},
|
|
};
|
|
expect(wrapper.vm.canMoveNote(note)).toBe(canMoveNoteResult);
|
|
},
|
|
);
|
|
});
|
|
});
|