Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ const cellStyles = '#container .dx-scheduler-cell-sizes-vertical { height: 100px
.click(scheduler.getAppointment('[Appointment 1]').element)
.pressKey('tab')
.click(scheduler.toolbar.viewSwitcher.element)
.pressKey('tab')
.pressKey('tab');

await t
Expand Down Expand Up @@ -251,7 +250,6 @@ test('should focus first visible appointment on tab (virtual scrolling)', async
await t
.scroll(scheduler.workspaceScrollable, 0, 1000)
.click(scheduler.toolbar.viewSwitcher.element)
.pressKey('tab')
.pressKey('tab');

await t
Expand All @@ -267,7 +265,6 @@ test('should focus first rendered appointment on tab (standard scrolling)', asyn
await t
.scroll(scheduler.workspaceScrollable, 0, 1000)
.click(scheduler.toolbar.viewSwitcher.element)
.pressKey('tab')
.pressKey('tab');

await t
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ export class SchedulerModel {
return new ToolbarModel(this.queries.getByRole('toolbar'));
}

getHeader(): HTMLElement {
const result = this.container.querySelector('.dx-scheduler-header');

if (!result) {
throw new Error('Scheduler header element not found');
}

return result as HTMLElement;
}

getWorkSpace(): HTMLElement {
const result = this.container.querySelector('.dx-scheduler-work-space');

if (!result) {
throw new Error('Scheduler workspace element not found');
}

return result as HTMLElement;
}

getStatusContent(): string {
const statusElement = this.container.querySelector('.dx-screen-reader-only');
return statusElement?.textContent ?? '';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
afterEach, beforeEach, describe, expect, it,
} from '@jest/globals';
import $ from '@js/core/renderer';

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

describe('Header', () => {
beforeEach(() => {
setupSchedulerTestEnvironment();
});

afterEach(() => {
const $scheduler = $(document.querySelector('.dx-scheduler'));
// @ts-expect-error
$scheduler.dxScheduler('dispose');
document.body.innerHTML = '';
});

it('should not have tabIndex attr', async () => {
const { POM } = await createScheduler({
dataSource: [],
currentView: 'day',
currentDate: new Date(2021, 4, 24),
});

expect(POM.getHeader().hasAttribute('tabindex')).toBe(false);
});

it('should not have tabIndex attr after tabIndex option change', async () => {
const { scheduler, POM } = await createScheduler({
dataSource: [],
currentView: 'day',
currentDate: new Date(2021, 4, 24),
});

scheduler.option('tabIndex', 1);

expect(POM.getHeader().hasAttribute('tabindex')).toBe(false);
});

describe('Toolbar', () => {
it('should have viewSwitcher with locateInMenu: "auto" by default', async () => {
const { scheduler } = await createScheduler({
dataSource: [],
currentView: 'day',
currentDate: new Date(2021, 4, 24),
});

const toolbarItems = scheduler.option('toolbar.items') as any[];
const viewSwitcherItem = toolbarItems.find((item: any) => item.name === 'viewSwitcher');

expect(viewSwitcherItem).toBeDefined();
expect(viewSwitcherItem.location).toBe('after');
expect(viewSwitcherItem.locateInMenu).toBe('auto');
});
});
});

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
afterEach, beforeEach, describe, expect, it,
} from '@jest/globals';
import $ from '@js/core/renderer';
import { fireEvent } from '@testing-library/dom';

import fx from '../../../common/core/animation/fx';
import CustomStore from '../../../data/custom_store';
import { createScheduler } from './__mock__/create_scheduler';
import { setupSchedulerTestEnvironment } from './__mock__/mock_scheduler';

const CLASSES = {
scheduler: 'dx-scheduler',
workSpace: 'dx-scheduler-work-space',
focusedState: 'dx-state-focused',
};

const defaultOptions = {
currentView: 'week',
views: ['week'],
currentDate: new Date(2024, 0, 1),
startDayHour: 9,
endDayHour: 16,
height: 600,
};

describe('Workspace', () => {
beforeEach(() => {
fx.off = true;
setupSchedulerTestEnvironment({ height: 600 });
});

afterEach(() => {
const $scheduler = $(document.querySelector(`.${CLASSES.scheduler}`));
// @ts-expect-error
$scheduler.dxScheduler('dispose');
document.body.innerHTML = '';
fx.off = false;
});

it('should not duplicate workspace elements when resources are loaded asynchronously (T661335)', async () => {
const { scheduler, container } = await createScheduler({
templatesRenderAsynchronously: true,
currentView: 'day',
views: ['day'],
groups: ['owner'],
resources: [
{
fieldExpr: 'owner',
dataSource: [{ id: 1, text: 'Owner 1' }],
},
{
fieldExpr: 'room',
dataSource: new CustomStore({
load(): Promise<unknown> {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, text: 'Room 1', color: '#ff0000' }]);
});
});
},
}),
},
],
dataSource: [
{
text: 'Meeting in Room 1',
startDate: new Date(2017, 4, 25, 9, 0),
endDate: new Date(2017, 4, 25, 10, 0),
roomId: 1,
},
],
startDayHour: 9,
currentDate: new Date(2017, 4, 25),
height: 600,
});

scheduler.option('groups', ['room']);

await new Promise((r) => { setTimeout(r); });

const $workSpaces = $(container).find(`.${CLASSES.workSpace}`);
const $groupHeader = $(container).find('.dx-scheduler-group-header');

expect($workSpaces.length).toBe(1);

expect($groupHeader.length).toBeGreaterThan(0);
expect($groupHeader.text()).toContain('Room 1');
});

describe('A11y', () => {
it('should have tabIndex -1 to be skipped in the tab order', async () => {
const { POM } = await createScheduler(defaultOptions);

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

it('should have tabIndex -1 after tabIndex option change', async () => {
const { scheduler, POM } = await createScheduler(defaultOptions);

scheduler.option('tabIndex', 1);

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

describe('Keyboard navigation', () => {
it('should focus the clicked cell', async () => {
const { POM } = await createScheduler(defaultOptions);

const cell = POM.getDateTableCell(0, 0);
fireEvent.mouseDown(cell, { which: 1 });
fireEvent.mouseUp(cell);

expect(cell.classList.contains(CLASSES.focusedState)).toBe(true);
});

it('should move focus and selection to the next cell on arrow key', async () => {
const { POM } = await createScheduler(defaultOptions);

const firstCell = POM.getDateTableCell(0, 0);
const secondCell = POM.getDateTableCell(1, 0);
fireEvent.mouseDown(firstCell, { which: 1 });
fireEvent.mouseUp(firstCell);
fireEvent.keyDown(POM.getWorkSpace(), { key: 'ArrowDown' });

expect(secondCell.classList.contains(CLASSES.focusedState)).toBe(true);
expect(firstCell.classList.contains(CLASSES.focusedState)).toBe(false);
});

it('should extend selection on shift + arrow key', async () => {
const { scheduler, POM } = await createScheduler(defaultOptions);

const firstCell = POM.getDateTableCell(0, 0);
fireEvent.mouseDown(firstCell, { which: 1 });
fireEvent.mouseUp(firstCell);
fireEvent.keyDown(POM.getWorkSpace(), { key: 'ArrowDown', shiftKey: true });

expect(scheduler.option('selectedCellData')).toHaveLength(2);
});

it('should clear focused cell when focus leaves the workspace', async () => {
const { POM } = await createScheduler(defaultOptions);

const cell = POM.getDateTableCell(0, 0);
fireEvent.mouseDown(cell, { which: 1 });
fireEvent.mouseUp(cell);
fireEvent.focusOut(POM.getWorkSpace());

expect(cell.classList.contains(CLASSES.focusedState)).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import registerComponent from '@js/core/component_registrator';
import devices from '@js/core/devices';
import errors from '@js/core/errors';
import $ from '@js/core/renderer';
import { noop } from '@js/core/utils/common';
import { getPathParts } from '@js/core/utils/data';
import dateUtils from '@js/core/utils/date';
import { extend } from '@js/core/utils/extend';
Expand Down Expand Up @@ -146,6 +147,8 @@ export class SchedulerHeader extends Widget<HeaderOptions> {
this._toggleVisibility();
}

_renderFocusTarget(): void { return noop(); }

private renderToolbar(): void {
const config = this.createToolbarConfig();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,10 @@ class SchedulerWorkSpace extends Widget<WorkspaceOptionsInternal> {
return this.$element();
}

_renderFocusTarget(): void {
this._focusTarget().attr('tabIndex', -1);
}
Comment thread
bit-byte0 marked this conversation as resolved.

protected isVerticalGroupedWorkSpace(): boolean { // TODO move to the Model
return Boolean(this.option().groups?.length) && this.option().groupOrientation === 'vertical';
}
Expand Down
Loading
Loading