Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dataSource from './init/widget.data';
import { createScheduler, scroll } from './init/widget.setup';
import url from '../../../../helpers/getPageUrl';
import { DEFAULT_BROWSER_SIZE } from '../../../../helpers/const';
import { Themes } from '../../../../helpers/themes';
import { testScreenshot } from '../../../../helpers/themeUtils';

fixture.disablePageReloads`Appointment tooltip behavior during scrolling in the Scheduler (T755449)`
Expand Down Expand Up @@ -125,6 +126,55 @@ test.meta({ browserSize: [600, 400] })('The tooltip should hide after manually s
}));
});

test.meta({
browserSize: DEFAULT_BROWSER_SIZE,
themes: [Themes.fluentBlue, Themes.genericLight, Themes.materialBlue],
})('Collector tooltip focused list item screenshot', async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
const scheduler = new Scheduler('#container');
const collector = scheduler.collectors.find('2 more');
const { appointmentTooltip } = scheduler;

await t
.expect(collector.element.exists)
.ok()
.click(collector.element)
.expect(appointmentTooltip.isVisible())
.ok()
.pressKey('tab');

await testScreenshot(
t,
takeScreenshot,
'collector-tooltip-focused-list-item.png',
{ element: scheduler.element },
);

await t
.expect(compareResults.isValid())
.ok(compareResults.errorMessages());
}).before(async () => createWidget('dxScheduler', {
dataSource: [{
text: 'Text',
startDate: new Date(2017, 4, 22, 9, 30, 0, 0),
endDate: new Date(2017, 4, 22, 10, 30, 0, 0),
}, {
text: 'Text2',
startDate: new Date(2017, 4, 22, 9, 30, 0, 0),
endDate: new Date(2017, 4, 22, 10, 30, 0, 0),
}, {
text: 'Text3',
startDate: new Date(2017, 4, 22, 9, 30, 0, 0),
endDate: new Date(2017, 4, 22, 10, 30, 0, 0),
}],
views: [{
type: 'month',
maxAppointmentsPerCell: 1,
}],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
}));

test.meta({ browserSize: [600, 1000] })('Tooltip on mobile devices should have enough hight if there are async templates (React)', async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
const scheduler = new Scheduler('#container');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ $scheduler-fill-focused-appointment: null !default;
$scheduler-is-shadow-color-for-focused-state: null !default;
$scheduler-dd-appointment-hover-text-color: null !default;
$scheduler-tooltip-appointment-text-color: null !default;
$scheduler-tooltip-list-focused-bg: null !default;
$scheduler-timeline-cell-height: null !default;

$scheduler-appointment-overlay-bg: null !default;
Expand All @@ -47,7 +48,8 @@ $agenda-appointment-recurrence-icon-color: null !default;
@use "./tooltip" with (
$scheduler-appointment-base-color: $scheduler-appointment-base-color,
$scheduler-tooltip-appointment-text-color: $scheduler-tooltip-appointment-text-color,
$scheduler-appointment-overlay-bg: $scheduler-appointment-overlay-bg
$scheduler-appointment-overlay-bg: $scheduler-appointment-overlay-bg,
$scheduler-tooltip-list-focused-bg: $scheduler-tooltip-list-focused-bg
);

$scheduler-appointment-min-size: 4px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
$scheduler-appointment-base-color: null !default;
$scheduler-tooltip-appointment-text-color: null !default;
$scheduler-appointment-overlay-bg: null !default;
$scheduler-tooltip-list-focused-bg: null !default;

.dx-popup-wrapper.dx-scheduler-appointment-tooltip-wrapper,
.dx-scheduler-overlay-panel {
Expand All @@ -15,6 +16,10 @@ $scheduler-appointment-overlay-bg: null !default;
.dx-list-item-content {
padding: 5px;
}

&.dx-state-focused {
background-color: $scheduler-tooltip-list-focused-bg;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@use "../colors" as *;
@use "sizes" as *;
@use "../sizes" as *;
@use "../list/colors" as *;
@use "../form/sizes" as *;
@use "../radioGroup/sizes" as *;
@use "../toolbar/sizes" as *;
Expand All @@ -13,6 +14,7 @@
@use "../../base/scheduler" as baseScheduler with (
$scheduler-tooltip-appointment-text-color: $scheduler-tooltip-appointment-text-color,
$scheduler-appointment-overlay-bg: $scheduler-appointment-overlay-bg,
$scheduler-tooltip-list-focused-bg: $list-item-hover-bg,
$scheduler-appointment-base-color: $scheduler-appointment-base-color,
$scheduler-appointment-start-color: $scheduler-appointment-start-color,
$scheduler-appointment-active-color: $scheduler-appointment-active-color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
@use "../colors" as *;
@use "sizes" as *;
@use "../sizes" as *;
@use "../list/colors" as *;
@use "../toolbar/sizes" as *;
@use '../../base/scheduler/layout/header' as *;
@use "../../base/mixins" as *;
@use "../../base/icon_fonts" as *;
@use "../../base/scheduler" as baseScheduler with (
$scheduler-tooltip-appointment-text-color: $scheduler-tooltip-appointment-text-color,
$scheduler-appointment-overlay-bg: $scheduler-appointment-overlay-bg,
$scheduler-tooltip-list-focused-bg: $list-item-hover-bg,
$scheduler-appointment-base-color: $scheduler-appointment-base-color,
$scheduler-appointment-start-color: $scheduler-appointment-start-color,
$scheduler-base-border-color: $scheduler-base-border-color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@use "../colors" as *;
@use "sizes" as *;
@use "../sizes" as *;
@use "../list/colors" as *;
@use "../radioGroup/sizes" as *;
@use "../toolbar/sizes" as *;
@use '../../base/scheduler/layout/header' as *;
Expand All @@ -12,6 +13,7 @@
@use "../../base/scheduler" as baseScheduler with (
$scheduler-tooltip-appointment-text-color: $scheduler-tooltip-appointment-text-color,
$scheduler-appointment-overlay-bg: $scheduler-appointment-overlay-bg,
$scheduler-tooltip-list-focused-bg: $list-item-hover-bg,
$scheduler-appointment-base-color: $scheduler-appointment-base-color,
$scheduler-appointment-start-color: $scheduler-appointment-start-color,
$scheduler-base-border-color: $scheduler-base-border-color,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { POPUP_DIALOG_CLASS } from '../../../m_scheduler';
import type { AppointmentModel } from './appointment';
import { createAppointmentModel } from './appointment';
import { PopupModel } from './popup';
import { TooltipModel } from './tooltip';

const getTexts = (
cells: ArrayLike<Element>,
Expand All @@ -25,6 +26,10 @@ export class SchedulerModel {
return this.getPopup();
}

get tooltip(): TooltipModel {
return new TooltipModel();
}

get toolbar(): ToolbarModel {
return new ToolbarModel(this.queries.getByRole('toolbar'));
}
Expand Down Expand Up @@ -55,6 +60,17 @@ export class SchedulerModel {
return getTexts(collectors);
}

getCollectorButton(index = 0): HTMLElement {
const allButtons = this.queries.queryAllByRole('button') as HTMLElement[];
const collectors = allButtons.filter((btn) => btn.classList.contains('dx-scheduler-appointment-collector'));

if (collectors.length === 0) {
throw new Error('Collector button not found');
}

return collectors[index];
}

getDateTableContent(): string[] {
const cells = this.container.querySelectorAll('.dx-scheduler-date-table-cell');
return getTexts(cells);
Expand Down Expand Up @@ -120,8 +136,6 @@ export class SchedulerModel {

getLoadPanel = (): HTMLElement | null => document.querySelector('.dx-loadpanel');

getTooltipAppointment = (): HTMLElement | null => document.querySelector('.dx-tooltip-appointment-item');

openPopupByDblClick(text?: string): AppointmentModel {
const appointment = this.getAppointment(text) as AppointmentModel;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { within } from '@testing-library/dom';

const TOOLTIP_WRAPPER_SELECTOR = '.dx-overlay-wrapper.dx-scheduler-appointment-tooltip-wrapper';

export class TooltipModel {
private get element(): HTMLElement | null {
return document.querySelector<HTMLElement>(TOOLTIP_WRAPPER_SELECTOR);
}

isVisible(): boolean {
return this.element !== null;
}

getScrollableContent(): Element | null {
return this.element?.querySelector('.dx-scrollable .dx-scrollview-content') ?? null;
}

getDeleteButton(index = 0): HTMLElement {
const tooltip = this.element;
const buttons = tooltip
? within(tooltip).queryAllByRole('button').filter((btn) => btn.classList.contains('dx-tooltip-appointment-item-delete-button'))
: [];

if (buttons.length === 0) {
throw new Error('Tooltip delete button not found');
}

return buttons[index];
}

getAppointmentItem(index = 0): HTMLElement | null {
const tooltip = this.element;
if (!tooltip) {
return null;
}
return within(tooltip).queryAllByRole('option')[index] ?? null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
afterEach, beforeEach, describe, expect, it, jest,
} from '@jest/globals';
import fx from '@js/common/core/animation/fx';

import { createScheduler } from './__mock__/create_scheduler';
import { setupSchedulerTestEnvironment } from './__mock__/m_mock_scheduler';

describe('Appointment tooltip behavior', () => {
beforeEach(() => {
fx.off = true;
setupSchedulerTestEnvironment();
});

afterEach(() => {
fx.off = false;
jest.useRealTimers();
document.body.innerHTML = '';
});

it('should delete appointment by Delete key when focused in tooltip from collector', async () => {
const { POM } = await createScheduler({
dataSource: [
{
text: 'Apt1',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
{
text: 'Apt2',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
],
views: [{ type: 'month', maxAppointmentsPerCell: 1 }],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
height: 600,
});

POM.getCollectorButton().click();

const tooltipScrollableContent = POM.tooltip.getScrollableContent();
tooltipScrollableContent?.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
tooltipScrollableContent?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));

expect(POM.tooltip.isVisible()).toBe(false);
});

it('should delete appointment on delete button click in tooltip', async () => {
const { POM } = await createScheduler({
dataSource: [
{
text: 'Apt1',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
{
text: 'Apt2',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
],
views: [{ type: 'month', maxAppointmentsPerCell: 1 }],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
height: 600,
});

POM.getCollectorButton().click();
POM.tooltip.getDeleteButton().click();

expect(POM.tooltip.isVisible()).toBe(false);
});

it('delete button in tooltip should not be focusable using tab', async () => {
const { POM } = await createScheduler({
dataSource: [
{
text: 'Apt1',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
{
text: 'Apt2',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
],
views: [{ type: 'month', maxAppointmentsPerCell: 1 }],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
height: 600,
});

POM.getCollectorButton().click();

expect(POM.tooltip.getDeleteButton().getAttribute('tabindex')).toBe('-1');
});

it('should not delete appointment by Delete key when editing.allowDeleting=false', async () => {
const { POM } = await createScheduler({
dataSource: [
{
text: 'Apt1',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
{
text: 'Apt2',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
],
views: [{ type: 'month', maxAppointmentsPerCell: 1 }],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
height: 600,
editing: {
allowDeleting: false,
},
});

POM.getCollectorButton().click();

const tooltipScrollableContent = POM.tooltip.getScrollableContent();
tooltipScrollableContent?.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
tooltipScrollableContent?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));

expect(POM.tooltip.isVisible()).toBe(true);
Copy link
Contributor

@Tucchhaa Tucchhaa Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we check that appointment is not deleted from dataSource instead of checking that tooltip is visible? I thought we have a task, to try not hide tooltip on appt delete. So, it would be less problems for us in the future if we remove this visibility check

Copy link
Contributor

@Tucchhaa Tucchhaa Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover the test doesn't actually check that appointment wasn't deleted...

});

it('should not delete disabled appointment by Delete key when focused in tooltip from collector', async () => {
const { POM } = await createScheduler({
dataSource: [
{
text: 'Apt1',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
},
{
text: 'Apt2',
startDate: new Date(2017, 4, 22, 9, 30),
endDate: new Date(2017, 4, 22, 10, 30),
disabled: true,
},
],
views: [{ type: 'month', maxAppointmentsPerCell: 1 }],
currentView: 'month',
currentDate: new Date(2017, 4, 22),
height: 600,
});

POM.getCollectorButton().click();

const tooltipScrollableContent = POM.tooltip.getScrollableContent();
tooltipScrollableContent?.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
tooltipScrollableContent?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true }));

expect(POM.tooltip.isVisible()).toBe(true);
Copy link
Contributor

@Tucchhaa Tucchhaa Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check datasource data instead of tooltip visibility here too?

});
});
Loading
Loading