mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2026-02-01 21:30:34 +01:00
341 lines
12 KiB
JavaScript
341 lines
12 KiB
JavaScript
import React from 'react';
|
|
import { expect } from 'chai';
|
|
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
import { shallow } from 'enzyme';
|
|
import sinon from 'sinon';
|
|
import Select from 'react-select';
|
|
|
|
import ScheduleDropdown from '../../../../app/component/routepage/schedule/ScheduleDropdown';
|
|
import { mockContext } from '../../helpers/mock-context';
|
|
import * as useTranslationsContext from '../../../../app/util/useTranslationsContext';
|
|
import * as ConfigContext from '../../../../app/configurations/ConfigContext';
|
|
|
|
describe('<ScheduleDropdown />', () => {
|
|
let defaultProps;
|
|
let useTranslationsContextStub;
|
|
let useConfigContextStub;
|
|
|
|
beforeEach(() => {
|
|
defaultProps = {
|
|
id: 'test-dropdown',
|
|
title: 'Test Title',
|
|
list: [
|
|
{ label: 'Option 1', value: 'opt1' },
|
|
{ label: 'Option 2', value: 'opt2' },
|
|
{ label: 'Option 3', value: 'opt3' },
|
|
],
|
|
onSelectChange: sinon.spy(),
|
|
alignRight: false,
|
|
labelId: undefined,
|
|
};
|
|
|
|
// Mock hooks
|
|
useTranslationsContextStub = sinon
|
|
.stub(useTranslationsContext, 'useTranslationsContext')
|
|
.returns({
|
|
formatMessage: sinon.stub().returns('translated text'),
|
|
locale: 'en',
|
|
});
|
|
|
|
useConfigContextStub = sinon
|
|
.stub(ConfigContext, 'useConfigContext')
|
|
.returns(mockContext.config);
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (useTranslationsContextStub) {
|
|
useTranslationsContextStub.restore();
|
|
}
|
|
if (useConfigContextStub) {
|
|
useConfigContextStub.restore();
|
|
}
|
|
});
|
|
|
|
it('should render without crashing', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
expect(wrapper.exists()).to.equal(true);
|
|
});
|
|
|
|
it('should render Select component', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
expect(wrapper.find(Select)).to.have.lengthOf(1);
|
|
});
|
|
|
|
it('should pass list options to Select', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
const options = select.prop('options');
|
|
|
|
expect(options).to.have.lengthOf(3);
|
|
});
|
|
|
|
it('should call onSelectChange when option is selected', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
const option = { value: 'opt2', label: 'Option 2', titleLabel: 'Option 2' };
|
|
select.prop('onChange')(option);
|
|
|
|
expect(defaultProps.onSelectChange.calledOnce).to.equal(true);
|
|
expect(defaultProps.onSelectChange.calledWith('opt2')).to.equal(true);
|
|
});
|
|
|
|
it('should apply alignRight class when alignRight prop is true', () => {
|
|
const props = { ...defaultProps, alignRight: true };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
|
|
const select = wrapper.find(Select);
|
|
expect(select.prop('classNamePrefix')).to.equal('dd-right');
|
|
});
|
|
|
|
it('should use dd classNamePrefix when alignRight is false', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('classNamePrefix')).to.equal('dd');
|
|
});
|
|
|
|
it('should use dd-timerange for other-dates dropdown when alignRight', () => {
|
|
const props = { ...defaultProps, id: 'other-dates', alignRight: true };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('classNamePrefix')).to.equal('dd-timerange');
|
|
});
|
|
|
|
it('should render label when labelId is provided', () => {
|
|
const props = { ...defaultProps, labelId: 'origin' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
|
|
const label = wrapper.find('label.dd-header-title');
|
|
expect(label).to.have.lengthOf(1);
|
|
});
|
|
|
|
it('should not render visible label when labelId is not provided', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
|
|
const visibleLabel = wrapper.find('label.dd-header-title').not('.sr-only');
|
|
expect(visibleLabel).to.have.lengthOf(0);
|
|
});
|
|
|
|
it('should render hidden label for accessibility when no labelId', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
|
|
const hiddenLabel = wrapper.find('label.sr-only');
|
|
expect(hiddenLabel).to.have.lengthOf(1);
|
|
});
|
|
|
|
it('should set isSearchable to false', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('isSearchable')).to.equal(false);
|
|
});
|
|
|
|
it('should render dropdown with correct id', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('name')).to.equal('test-dropdown');
|
|
expect(select.prop('inputId')).to.equal('aria-input-test-dropdown');
|
|
});
|
|
|
|
it('should truncate long titles in placeholder', () => {
|
|
const props = {
|
|
...defaultProps,
|
|
title: 'Very Long Title That Should Be Truncated',
|
|
};
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
|
|
// Should render but truncate in placeholder
|
|
expect(wrapper.exists()).to.equal(true);
|
|
});
|
|
|
|
it('should add check icon to options matching title', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
const options = select.prop('options');
|
|
|
|
// Options are enhanced with titleLabel and check icons
|
|
expect(options).to.be.an('array');
|
|
});
|
|
|
|
it('should handle menu open state', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
select.prop('onMenuOpen')();
|
|
wrapper.update();
|
|
|
|
const updatedSelect = wrapper.find(Select);
|
|
expect(updatedSelect.prop('menuIsOpen')).to.equal(true);
|
|
});
|
|
|
|
it('should handle menu close state', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
// Open then close
|
|
select.prop('onMenuOpen')();
|
|
wrapper.update();
|
|
wrapper.find(Select).prop('onMenuClose')();
|
|
wrapper.update();
|
|
|
|
const updatedSelect = wrapper.find(Select);
|
|
expect(updatedSelect.prop('menuIsOpen')).to.equal(false);
|
|
});
|
|
|
|
it('should handle selection when no id is provided', () => {
|
|
const props = { ...defaultProps, id: '' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
const option = { value: 'opt1', label: 'Option 1', titleLabel: 'Option 1' };
|
|
select.prop('onChange')(option);
|
|
|
|
expect(defaultProps.onSelectChange.called).to.equal(true);
|
|
});
|
|
|
|
it('should apply withLabel class when labelId is provided', () => {
|
|
const props = { ...defaultProps, labelId: 'origin' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
|
|
expect(wrapper.find('.dd-container').hasClass('withLabel')).to.equal(true);
|
|
});
|
|
|
|
it('should not apply withLabel class when no labelId', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
|
|
expect(wrapper.find('.dd-container').hasClass('withLabel')).to.equal(false);
|
|
});
|
|
|
|
it('should have aria-live="off" on container', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
|
|
expect(wrapper.find('.dd-container').prop('aria-live')).to.equal('off');
|
|
});
|
|
|
|
it('should pass aria-labelledby to Select', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('aria-labelledby')).to.equal('aria-label-test-dropdown');
|
|
});
|
|
|
|
it('should have proper aria-live messages', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('ariaLiveMessages')).to.be.an('object');
|
|
expect(select.prop('ariaLiveMessages')).to.have.property('guidance');
|
|
expect(select.prop('ariaLiveMessages')).to.have.property('onChange');
|
|
expect(select.prop('ariaLiveMessages')).to.have.property('onFilter');
|
|
expect(select.prop('ariaLiveMessages')).to.have.property('onFocus');
|
|
});
|
|
|
|
it('should remove dropdown indicator and separator', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const select = wrapper.find(Select);
|
|
const components = select.prop('components');
|
|
|
|
expect(components.DropdownIndicator()).to.equal(null);
|
|
expect(components.IndicatorSeparator()).to.equal(null);
|
|
});
|
|
|
|
it('should handle list with single option', () => {
|
|
const props = {
|
|
...defaultProps,
|
|
list: [{ label: 'Only Option', value: 'only' }],
|
|
};
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('options')).to.have.lengthOf(1);
|
|
});
|
|
|
|
it('should handle list with many options', () => {
|
|
const manyOptions = Array.from({ length: 50 }, (_, i) => ({
|
|
label: `Option ${i}`,
|
|
value: `opt${i}`,
|
|
}));
|
|
const props = { ...defaultProps, list: manyOptions };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('options')).to.have.lengthOf(50);
|
|
});
|
|
|
|
it('should use default title when title prop is not provided', () => {
|
|
const props = { ...defaultProps, title: undefined };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
|
|
// Component should use defaultProps title
|
|
expect(wrapper.exists()).to.equal(true);
|
|
});
|
|
|
|
it('should handle options with special characters', () => {
|
|
const props = {
|
|
...defaultProps,
|
|
list: [
|
|
{ label: 'Käpylä (Helsinki)', value: 'kapyla' },
|
|
{ label: 'Töölö / Tölö', value: 'toolo' },
|
|
],
|
|
};
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('options')).to.have.lengthOf(2);
|
|
});
|
|
|
|
it('should handle empty list gracefully', () => {
|
|
const props = { ...defaultProps, list: [] };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(select.prop('options')).to.have.lengthOf(0);
|
|
});
|
|
|
|
it('should align label right when alignRight and labelId provided', () => {
|
|
const props = { ...defaultProps, alignRight: true, labelId: 'destination' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const label = wrapper.find('label.dd-header-title');
|
|
|
|
expect(label.hasClass('alignRight')).to.equal(true);
|
|
});
|
|
|
|
it('should not align label right when alignRight is false', () => {
|
|
const props = { ...defaultProps, alignRight: false, labelId: 'origin' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const label = wrapper.find('label.dd-header-title');
|
|
|
|
expect(label.hasClass('alignRight')).to.equal(false);
|
|
});
|
|
|
|
it('should handle selection without onSelectChange callback', () => {
|
|
const props = { ...defaultProps, onSelectChange: undefined };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const select = wrapper.find(Select);
|
|
|
|
const option = { value: 'opt1', label: 'Option 1', titleLabel: 'Option 1' };
|
|
// Should not throw error
|
|
expect(() => select.prop('onChange')(option)).to.not.throw();
|
|
});
|
|
|
|
it('should have correct htmlFor attribute on label', () => {
|
|
const props = { ...defaultProps, labelId: 'origin' };
|
|
const wrapper = shallow(<ScheduleDropdown {...props} />);
|
|
const label = wrapper.find('label.dd-header-title');
|
|
|
|
expect(label.prop('htmlFor')).to.equal('aria-input-test-dropdown');
|
|
});
|
|
|
|
it('should match label and input ids for accessibility', () => {
|
|
const wrapper = shallow(<ScheduleDropdown {...defaultProps} />);
|
|
const label = wrapper.find('label').first();
|
|
const select = wrapper.find(Select);
|
|
|
|
expect(label.prop('id')).to.equal('aria-label-test-dropdown');
|
|
expect(select.prop('inputId')).to.equal('aria-input-test-dropdown');
|
|
expect(select.prop('aria-labelledby')).to.equal('aria-label-test-dropdown');
|
|
});
|
|
});
|