Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication: Fix dropdown menu behavior for links to allow default browser options #9832

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
Renderer2,
Expand Down Expand Up @@ -36,7 +37,7 @@ import { AnswerPostReactionsBarComponent } from 'app/shared/metis/posting-reacti
]),
],
})
export class AnswerPostComponent extends PostingDirective<AnswerPost> implements OnInit, OnChanges {
export class AnswerPostComponent extends PostingDirective<AnswerPost> implements OnInit, OnChanges, OnDestroy {
@Input() lastReadDate?: dayjs.Dayjs;
@Input() isLastAnswer: boolean;
@Output() openPostingCreateEditModal = new EventEmitter<void>();
Expand Down Expand Up @@ -119,24 +120,33 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> implements
}

onRightClick(event: MouseEvent) {
event.preventDefault();

if (AnswerPostComponent.activeDropdownPost && AnswerPostComponent.activeDropdownPost !== this) {
AnswerPostComponent.activeDropdownPost.showDropdown = false;
AnswerPostComponent.activeDropdownPost.enableBodyScroll();
AnswerPostComponent.activeDropdownPost.changeDetector.detectChanges();
const targetElement = event.target as HTMLElement;
let isPointerCursor = false;
try {
isPointerCursor = window.getComputedStyle(targetElement).cursor === 'pointer';
} catch (error) {
console.error('Failed to compute style:', error);
asliayk marked this conversation as resolved.
Show resolved Hide resolved
isPointerCursor = true;
}

AnswerPostComponent.activeDropdownPost = this;
if (!isPointerCursor) {
event.preventDefault();

this.dropdownPosition = {
x: event.clientX,
y: event.clientY,
};
if (AnswerPostComponent.activeDropdownPost !== this) {
AnswerPostComponent.cleanupActiveDropdown();
}

this.showDropdown = true;
this.adjustDropdownPosition();
this.disableBodyScroll();
AnswerPostComponent.activeDropdownPost = this;

this.dropdownPosition = {
x: event.clientX,
y: event.clientY,
};

this.showDropdown = true;
this.adjustDropdownPosition();
this.disableBodyScroll();
}
asliayk marked this conversation as resolved.
Show resolved Hide resolved
}

adjustDropdownPosition() {
Expand All @@ -148,6 +158,21 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> implements
}
}

private static cleanupActiveDropdown(): void {
if (AnswerPostComponent.activeDropdownPost) {
AnswerPostComponent.activeDropdownPost.showDropdown = false;
AnswerPostComponent.activeDropdownPost.enableBodyScroll();
AnswerPostComponent.activeDropdownPost.changeDetector.detectChanges();
AnswerPostComponent.activeDropdownPost = null;
}
}

ngOnDestroy(): void {
if (AnswerPostComponent.activeDropdownPost === this) {
AnswerPostComponent.cleanupActiveDropdown();
}
}
asliayk marked this conversation as resolved.
Show resolved Hide resolved

private assignPostingToAnswerPost() {
// This is needed because otherwise instanceof returns 'object'.
if (this.posting && !(this.posting instanceof AnswerPost)) {
Expand Down
33 changes: 19 additions & 14 deletions src/main/webapp/app/shared/metis/post/post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,24 +128,29 @@ export class PostComponent extends PostingDirective<Post> implements OnInit, OnC
}

onRightClick(event: MouseEvent) {
event.preventDefault();
const targetElement = event.target as HTMLElement;
const isPointerCursor = window.getComputedStyle(targetElement).cursor === 'pointer';

if (PostComponent.activeDropdownPost && PostComponent.activeDropdownPost !== this) {
PostComponent.activeDropdownPost.showDropdown = false;
PostComponent.activeDropdownPost.enableBodyScroll();
PostComponent.activeDropdownPost.changeDetector.detectChanges();
}
if (!isPointerCursor) {
event.preventDefault();

if (PostComponent.activeDropdownPost && PostComponent.activeDropdownPost !== this) {
PostComponent.activeDropdownPost.showDropdown = false;
PostComponent.activeDropdownPost.enableBodyScroll();
PostComponent.activeDropdownPost.changeDetector.detectChanges();
}

PostComponent.activeDropdownPost = this;
PostComponent.activeDropdownPost = this;

this.dropdownPosition = {
x: event.clientX,
y: event.clientY,
};
this.dropdownPosition = {
x: event.clientX,
y: event.clientY,
};

this.showDropdown = true;
this.adjustDropdownPosition();
this.disableBodyScroll();
this.showDropdown = true;
this.adjustDropdownPosition();
this.disableBodyScroll();
}
}

adjustDropdownPosition() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ describe('AnswerPostComponent', () => {
});
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should contain an answer post header when isConsecutive is false', () => {
runInInjectionContext(fixture.debugElement.injector, () => {
component.isConsecutive = input<boolean>(false);
Expand Down Expand Up @@ -178,6 +182,42 @@ describe('AnswerPostComponent', () => {
expect(component.posting.reactions).toEqual(updatedReactions);
});

it('should handle onRightClick correctly based on cursor style', () => {
const testCases = [
{
cursor: 'pointer',
preventDefaultCalled: false,
showDropdown: false,
dropdownPosition: { x: 0, y: 0 },
},
{
cursor: 'default',
preventDefaultCalled: true,
showDropdown: true,
dropdownPosition: { x: 100, y: 200 },
},
];

testCases.forEach(({ cursor, preventDefaultCalled, showDropdown, dropdownPosition }) => {
const event = new MouseEvent('contextmenu', { clientX: 100, clientY: 200 });

const targetElement = document.createElement('div');
Object.defineProperty(event, 'target', { value: targetElement });

jest.spyOn(window, 'getComputedStyle').mockReturnValue({
cursor,
} as CSSStyleDeclaration);

const preventDefaultSpy = jest.spyOn(event, 'preventDefault');

component.onRightClick(event);

expect(preventDefaultSpy).toHaveBeenCalledTimes(preventDefaultCalled ? 1 : 0);
expect(component.showDropdown).toBe(showDropdown);
expect(component.dropdownPosition).toEqual(dropdownPosition);
});
});

it('should cast the post to answer post on change', () => {
const mockPost: Posting = {
id: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,44 @@ describe('PostComponent', () => {
expect(enableBodyScrollSpy).toHaveBeenCalled();
});

it('should handle onRightClick correctly based on cursor style', () => {
const testCases = [
{
cursor: 'pointer',
preventDefaultCalled: false,
showDropdown: false,
dropdownPosition: { x: 0, y: 0 },
},
{
cursor: 'default',
preventDefaultCalled: true,
showDropdown: true,
dropdownPosition: { x: 100, y: 200 },
},
];

testCases.forEach(({ cursor, preventDefaultCalled, showDropdown, dropdownPosition }) => {
const event = new MouseEvent('contextmenu', { clientX: 100, clientY: 200 });

const targetElement = document.createElement('div');
Object.defineProperty(event, 'target', { value: targetElement });

jest.spyOn(window, 'getComputedStyle').mockReturnValue({
cursor,
} as CSSStyleDeclaration);

const preventDefaultSpy = jest.spyOn(event, 'preventDefault');

component.onRightClick(event);

expect(preventDefaultSpy).toHaveBeenCalledTimes(preventDefaultCalled ? 1 : 0);
expect(component.showDropdown).toBe(showDropdown);
expect(component.dropdownPosition).toEqual(dropdownPosition);

jest.restoreAllMocks();
});
});

it('should cast the post to Post on change', () => {
const mockPost: Posting = {
id: 1,
Expand Down
Loading