String(this.maxValue).length)
- newValue = numberParser.parse(String(newValue).slice(1));
-
- const { minValue, maxValue } = this.inputValidationLimits;
-
- if (newValue < minValue) {
- newValue = !this.isInputValueCompliant(typedValue)
- ? this.value
- : typedValue;
- } else if (newValue > maxValue) {
- newValue = !this.isInputValueCompliant(typedValue)
- ? this.value
- : typedValue;
+ if (this.isInputValueCompliant(newValue)) {
+ this.value = newValue;
+ return;
}
- this.value = newValue;
+ if (this.isInputValueCompliant(typedValue)) this.value = typedValue;
}
private isInputValueCompliant(value: number): boolean {
diff --git a/packages/date-time-picker/src/segments/SegmentsFactory.ts b/packages/date-time-picker/src/segments/SegmentsFactory.ts
index 4e2e619bfe..d95433a16f 100644
--- a/packages/date-time-picker/src/segments/SegmentsFactory.ts
+++ b/packages/date-time-picker/src/segments/SegmentsFactory.ts
@@ -10,7 +10,6 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/
-// import { ZonedDateTime } from '@internationalized/date';
import { DateFormatter, ZonedDateTime } from '@internationalized/date';
import { NumberFormatter } from '@internationalized/number';
import { SegmentType, SegmentTypes } from '../types';
diff --git a/packages/date-time-picker/stories/date-time-picker.stories.ts b/packages/date-time-picker/stories/date-time-picker.stories.ts
index f5bd99fff8..ffb890a24d 100644
--- a/packages/date-time-picker/stories/date-time-picker.stories.ts
+++ b/packages/date-time-picker/stories/date-time-picker.stories.ts
@@ -12,15 +12,16 @@ governing permissions and limitations under the License.
import {
CalendarDate,
CalendarDateTime,
+ DateValue,
toZoned,
} from '@internationalized/date';
+
import {
css,
html,
TemplateResult,
unsafeCSS,
} from '@spectrum-web-components/base';
-import { DateValue } from '@spectrum-web-components/calendar';
import {
DateTimePickerValue,
Precision,
@@ -115,35 +116,45 @@ const storyMeta = {
},
};
-const timestampToValue = (timestamp: number): DateValue => {
- const date = new Date();
- date.setTime(timestamp);
- return new CalendarDateTime(
- date.getFullYear(),
- date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed
- date.getDate(),
- date.getHours(),
- date.getMinutes(),
- date.getSeconds()
- );
+const computeProps = (args: StoryArgs): ComponentArgs => {
+ const timestampToValue = (timestamp: number): DateValue => {
+ const date = new Date();
+ date.setTime(timestamp);
+ return new CalendarDateTime(
+ date.getFullYear(),
+ date.getMonth() + 1, // Date months are 0-indexed while CalendarDate months are 1-indexed
+ date.getDate(),
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds()
+ );
+ };
+
+ return {
+ value: args.value
+ ? timestampToValue(args.value as unknown as number)
+ : undefined,
+ min: args.min
+ ? timestampToValue(args.min as unknown as number)
+ : undefined,
+ max: args.max
+ ? timestampToValue(args.max as unknown as number)
+ : undefined,
+ disabled: args.disabled,
+ readonly: args.readonly,
+ quiet: args.quiet,
+ invalid: args.invalid,
+ autofocus: args.autofocus,
+ precision: args.precision,
+ };
};
const Template = (args: StoryArgs = {}): TemplateResult => {
- args.value = args.value
- ? timestampToValue(args.value as unknown as number)
- : undefined;
- args.min = args.min
- ? timestampToValue(args.min as unknown as number)
- : undefined;
- args.max = args.max
- ? timestampToValue(args.max as unknown as number)
- : undefined;
-
return html`
`;
};
@@ -175,84 +186,65 @@ autofocus.args = {
autofocus: true,
};
-const dateControlsDisabledArgTypes = {
- min: {
- table: {
- disable: true,
- },
- },
- max: {
- table: {
- disable: true,
- },
- },
- value: {
- table: {
- disable: true,
- },
- },
-};
-
export const preselectedValue = (args: StoryArgs): TemplateResult => {
return html`
`;
};
-preselectedValue.argTypes = dateControlsDisabledArgTypes;
export const minDate = (args: StoryArgs): TemplateResult => {
return html`
`;
};
-minDate.argTypes = dateControlsDisabledArgTypes;
export const maxDate = (args: StoryArgs): TemplateResult => {
return html`
`;
};
-maxDate.argTypes = dateControlsDisabledArgTypes;
export const minAndMaxDates = (args: StoryArgs): TemplateResult => {
return html`
`;
};
-minAndMaxDates.argTypes = dateControlsDisabledArgTypes;
export const secondPrecision = (args: StoryArgs): TemplateResult => {
return html`
`;
};
export const helpText = (args: StoryArgs): TemplateResult => {
return html`
-
+
Please select your birthday
@@ -263,9 +255,9 @@ export const helpText = (args: StoryArgs): TemplateResult => {
export const negativeHelpText = (args: StoryArgs): TemplateResult => {
return html`
Change state to invalid to see the negative help text
@@ -276,11 +268,10 @@ export const negativeHelpText = (args: StoryArgs): TemplateResult => {
`;
};
-negativeHelpText.argTypes = dateControlsDisabledArgTypes;
-export const customIcon = (): TemplateResult => {
+export const customIcon = (args: StoryArgs): TemplateResult => {
return html`
-
+
`;
@@ -303,7 +294,7 @@ export const customWidth = (args: StoryArgs): TemplateResult[] => {
`;
diff --git a/packages/date-time-picker/test/date-time-picker.test.ts b/packages/date-time-picker/test/date-time-picker.test.ts
index 88be449543..1888296d07 100644
--- a/packages/date-time-picker/test/date-time-picker.test.ts
+++ b/packages/date-time-picker/test/date-time-picker.test.ts
@@ -18,13 +18,7 @@ import {
ZonedDateTime,
} from '@internationalized/date';
import { NumberFormatter } from '@internationalized/number';
-import {
- elementUpdated,
- expect,
- fixture,
- html,
- oneEvent,
-} from '@open-wc/testing';
+
import { Calendar } from '@spectrum-web-components/calendar';
import {
DateTimePicker,
@@ -34,17 +28,28 @@ import {
SegmentTypes,
} from '@spectrum-web-components/date-time-picker';
import { PickerButton } from '@spectrum-web-components/picker-button';
+
+import {
+ elementUpdated,
+ expect,
+ fixture,
+ html,
+ oneEvent,
+} from '@open-wc/testing';
import { sendKeys, sendMouse } from '@web/test-runner-commands';
import { spy, stub } from 'sinon';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
import {
dispatchCalendarChange,
type EditableSegments,
+ expectFocused,
expectPlaceholder,
expectPlaceholders,
expectSameDates,
fixtureElement,
getEditableSegments,
+ getElementCenter,
+ isCalendarOpen,
openCalendar,
sendKeyMultipleTimes,
} from './helpers.js';
@@ -157,7 +162,9 @@ describe('DateTimePicker', () => {
});
it('should update the value as CalendarDate when it is the most specific date value', async () => {
- element = await fixtureElement({ props: { value: valueDate } });
+ element = await fixtureElement({
+ props: { value: valueDate.set({ year: 2222 }) },
+ });
editableSegments = getEditableSegments(element);
const year = editableSegments.getByType(SegmentTypes.Year);
@@ -195,7 +202,9 @@ describe('DateTimePicker', () => {
});
it('should update the value as CalendarDateTime when it is the most specific date value', async () => {
- element = await fixtureElement({ props: { value: valueDateTime } });
+ element = await fixtureElement({
+ props: { value: valueDateTime.set({ year: 2222 }) },
+ });
editableSegments = getEditableSegments(element);
const year = editableSegments.getByType(SegmentTypes.Year);
@@ -237,7 +246,9 @@ describe('DateTimePicker', () => {
});
it('should update the value as ZonedDateTime when it is the most specific date value', async () => {
- element = await fixtureElement({ props: { value: valueZoned } });
+ element = await fixtureElement({
+ props: { value: valueZoned.set({ year: 2222 }) },
+ });
editableSegments = getEditableSegments(element);
const year = editableSegments.getByType(SegmentTypes.Year);
@@ -580,7 +591,7 @@ describe('DateTimePicker', () => {
describe('Calendar', () => {
it('should have the calendar closed by default', () => {
- expect(element['isCalendarOpen']).to.be.false;
+ expect(isCalendarOpen(element)).to.be.false;
});
it('should open and close the calendar using the keyboard', async () => {
@@ -593,13 +604,13 @@ describe('DateTimePicker', () => {
await sendKeys({ press: 'Enter' });
await opened;
- expect(element['isCalendarOpen']).to.be.true;
+ expect(isCalendarOpen(element)).to.be.true;
const closed = oneEvent(element, 'sp-closed');
await sendKeys({ press: 'Escape' });
await closed;
- expect(element['isCalendarOpen']).to.be.false;
+ expect(isCalendarOpen(element)).to.be.false;
});
it('should open and close the calendar using the pointer', async () => {
@@ -607,18 +618,14 @@ describe('DateTimePicker', () => {
'sp-picker-button'
) as PickerButton;
- const rect = calendarButton.getBoundingClientRect();
- const centerX = Math.round(rect.left + rect.width / 2);
- const centerY = Math.round(rect.top + rect.height / 2);
-
const opened = oneEvent(element, 'sp-opened');
await sendMouse({
type: 'click',
- position: [centerX, centerY],
+ position: getElementCenter(calendarButton),
});
await opened;
- expect(element['isCalendarOpen']).to.be.true;
+ expect(isCalendarOpen(element)).to.be.true;
const closed = oneEvent(element, 'sp-closed');
await sendMouse({
@@ -627,7 +634,7 @@ describe('DateTimePicker', () => {
});
await closed;
- expect(element['isCalendarOpen']).to.be.false;
+ expect(isCalendarOpen(element)).to.be.false;
});
it('should pass the value and min/max constraints to the calendar', async () => {
@@ -656,10 +663,20 @@ describe('DateTimePicker', () => {
);
});
+ it("should close the calendar when the component gets disabled and it's open", async () => {
+ await openCalendar(element);
+ expect(isCalendarOpen(element)).to.be.true;
+
+ element.disabled = true;
+ await elementUpdated(element);
+
+ expect(isCalendarOpen(element)).to.be.false;
+ });
+
describe('change event', () => {
it('should close the calendar when handling its change event', async () => {
await openCalendar(element);
- expect(element['isCalendarOpen']).to.be.true;
+ expect(isCalendarOpen(element)).to.be.true;
const closed = oneEvent(element, 'sp-closed');
await dispatchCalendarChange(
@@ -668,7 +685,7 @@ describe('DateTimePicker', () => {
);
await closed;
- expect(element['isCalendarOpen']).to.be.false;
+ expect(isCalendarOpen(element)).to.be.false;
});
it('should update the value as CalendarDate when its the most specific date value', async () => {
@@ -897,17 +914,187 @@ describe('DateTimePicker', () => {
});
describe('Focus', () => {
- it('should focus segments by clicking on them');
- it(
- "should change segment focus to right by using the 'Right Arrow' key"
- );
- it("should change segment focus to left by using the 'Left Arrow' key");
- it(
- 'should change segment focus to left by using the Backspace/Delete key on a placeholder'
- );
- // TODO: one TAB press should focus the calendar button, not the next segment
- it("should focus the calendar button by using the 'Tab' key");
- it('should focus the calendar button after the Calendar closes');
+ it('should focus segments by clicking on them', async () => {
+ const yearSegment = editableSegments.getByType(SegmentTypes.Year);
+
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(yearSegment),
+ });
+ await sendKeys({ press: 'ArrowUp' });
+
+ expectFocused(document, element, 'element not focused');
+ expectFocused(element.shadowRoot, yearSegment, 'year not focused');
+ expect(yearSegment.innerText).to.equal(`${fixedYear}`);
+
+ const daySegment = editableSegments.getByType(SegmentTypes.Day);
+
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(daySegment),
+ });
+ await sendKeys({ press: 'ArrowUp' });
+
+ expectFocused(document, element, 'element not focused');
+ expectFocused(element.shadowRoot, daySegment, 'day not focused');
+ expect(daySegment.innerText).to.equal('01');
+ });
+
+ it('should focus the first editable segment when the focus method is called', async () => {
+ element.focus();
+ await elementUpdated(element);
+
+ expectFocused(document, element, 'element not focused');
+ expectFocused(element.shadowRoot, element.firstEditableSegment);
+ });
+
+ it("should change segment focus to right by using the 'Right Arrow' key", async () => {
+ element = await fixtureElement({
+ props: { precision: Precisions.Second },
+ });
+ editableSegments = getEditableSegments(element);
+ const dayPeriodSegment = editableSegments.getByType(
+ SegmentTypes.DayPeriod
+ );
+
+ element.focus();
+ await elementUpdated(element);
+ await sendKeyMultipleTimes(
+ 'ArrowRight',
+ editableSegments.length - 1
+ );
+
+ expectFocused(element.shadowRoot, dayPeriodSegment);
+
+ await sendKeys({ press: 'ArrowRight' });
+
+ expectFocused(
+ element.shadowRoot,
+ dayPeriodSegment,
+ 'dayPeriod no longer focused'
+ );
+ });
+
+ it("should change segment focus to left by using the 'Left Arrow' key", async () => {
+ element = await fixtureElement({
+ props: { precision: Precisions.Second },
+ });
+ editableSegments = getEditableSegments(element);
+ const dayPeriodSegment = editableSegments.getByType(
+ SegmentTypes.DayPeriod
+ );
+
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(dayPeriodSegment),
+ });
+
+ expectFocused(document, element, 'element not focused');
+ expectFocused(
+ element.shadowRoot,
+ dayPeriodSegment,
+ 'dayPeriod not focused'
+ );
+
+ await sendKeyMultipleTimes(
+ 'ArrowLeft',
+ editableSegments.length - 1
+ );
+
+ expectFocused(element.shadowRoot, element.firstEditableSegment);
+
+ await sendKeys({ press: 'ArrowLeft' });
+
+ expectFocused(
+ element.shadowRoot,
+ element.firstEditableSegment,
+ 'firstEditableSegment no longer focused'
+ );
+ });
+
+ it('should change segment focus to left by using the Backspace/Delete key on a placeholder', async () => {
+ element = await fixtureElement({
+ props: {
+ precision: Precisions.Hour,
+ },
+ });
+
+ editableSegments = getEditableSegments(element);
+ expectPlaceholders(editableSegments);
+
+ const dayPeriod = editableSegments.getByType(
+ SegmentTypes.DayPeriod
+ );
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(dayPeriod),
+ });
+
+ expectFocused(document, element, 'element not focused');
+ expectFocused(
+ element.shadowRoot,
+ dayPeriod,
+ 'dayPeriod not focused'
+ );
+
+ await sendKeys({ press: 'Backspace' });
+
+ const hourSegment = editableSegments.getByType(SegmentTypes.Hour);
+ expectFocused(element.shadowRoot, hourSegment, 'hour not focused');
+
+ await sendKeys({ press: 'Backspace' });
+
+ const yearSegment = editableSegments.getByType(SegmentTypes.Year);
+ expectFocused(element.shadowRoot, yearSegment, 'year not focused');
+
+ await sendKeys({ press: 'Delete' });
+
+ const daySegment = editableSegments.getByType(SegmentTypes.Day);
+ expectFocused(element.shadowRoot, daySegment, 'day not focused');
+
+ await sendKeys({ press: 'Delete' });
+
+ const monthSegment = editableSegments.getByType(SegmentTypes.Month);
+ expectFocused(
+ element.shadowRoot,
+ monthSegment,
+ 'month not focused'
+ );
+
+ await sendKeys({ press: 'Delete' });
+ expectFocused(
+ element.shadowRoot,
+ monthSegment,
+ 'month not focused'
+ );
+ });
+
+ it("should change focus up to the calendar button by using the 'Tab' key", async () => {
+ element.focus();
+ await elementUpdated(element);
+ const dayPeriodSegment = editableSegments.getByType(
+ SegmentTypes.DayPeriod
+ );
+ const calendarButton = element.shadowRoot!.querySelector(
+ 'sp-picker-button'
+ ) as PickerButton;
+
+ await sendKeyMultipleTimes('Tab', editableSegments.length - 1);
+
+ expectFocused(
+ element.shadowRoot,
+ dayPeriodSegment,
+ 'dayPeriod not focused'
+ );
+
+ await sendKeys({ press: 'Tab' });
+
+ expectFocused(
+ element.shadowRoot,
+ calendarButton,
+ 'calendarButton not focused'
+ );
+ });
});
describe('ArrowUp key', () => {
@@ -1995,7 +2182,7 @@ describe('DateTimePicker', () => {
await sendKeys({ type: '0' });
expect(segment.innerText).to.equal('2030');
await sendKeys({ type: '5' });
- expect(segment.innerText).to.equal('305');
+ expect(segment.innerText).to.equal('5');
expectPlaceholders(editableSegments, [segment]);
expect(element.value).to.be.undefined;
@@ -2247,7 +2434,7 @@ describe('DateTimePicker', () => {
expect(segment.innerText).to.equal('12');
await sendKeys({ type: '3' });
await elementUpdated(element);
- expect(segment.innerText).to.equal('23');
+ expect(segment.innerText).to.equal('03');
await sendKeys({ type: '5' });
await elementUpdated(element);
expect(segment.innerText).to.equal('05');
@@ -2663,7 +2850,7 @@ describe('DateTimePicker', () => {
element = await fixtureElement({
props: {
precision: Precisions.Second,
- value: new CalendarDateTime(1010, 10, 15, 13, 10, 10),
+ value: new CalendarDateTime(1010, 10, 15, 12, 10, 10),
},
});
element.addEventListener('input', inputSpy);
@@ -2851,7 +3038,6 @@ describe('DateTimePicker', () => {
inputSpy.resetHistory();
await sendKeys({ press: 'Delete' });
- await sendKeys({ press: 'Delete' });
await elementUpdated(element);
expect(inputSpy.callCount).to.equal(0);
@@ -2884,7 +3070,6 @@ describe('DateTimePicker', () => {
inputSpy.resetHistory();
await sendKeys({ press: 'Delete' });
- await sendKeys({ press: 'Delete' });
await elementUpdated(element);
expect(inputSpy.callCount).to.equal(0);
@@ -2900,7 +3085,6 @@ describe('DateTimePicker', () => {
});
element.addEventListener('input', inputSpy);
editableSegments = getEditableSegments(element);
- const yearSegment = editableSegments.getByType(SegmentTypes.Year);
const monthSegment = editableSegments.getByType(SegmentTypes.Month);
const hourSegment = editableSegments.getByType(SegmentTypes.Hour);
const secondSegment = editableSegments.getByType(
@@ -2910,10 +3094,6 @@ describe('DateTimePicker', () => {
SegmentTypes.DayPeriod
);
- yearSegment.focus();
- await sendKeys({ type: '222' });
- await elementUpdated(element);
-
monthSegment.focus();
await sendKeys({ type: '5' });
await elementUpdated(element);
@@ -4227,10 +4407,44 @@ describe('DateTimePicker', () => {
});
describe('Disabled', () => {
- it('should not accept focus');
- it('should not accept typed in values');
- it('should not accept arrow key inputs');
- it('should not open the calendar');
+ beforeEach(async () => {
+ element = await fixtureElement({ props: { disabled: true } });
+ editableSegments = getEditableSegments(element);
+ });
+
+ it('should not accept focus', async () => {
+ element.focus();
+ await elementUpdated(element);
+ expect(document.activeElement === element).to.be.false;
+
+ const yearSegment = editableSegments.getByType(SegmentTypes.Year);
+ yearSegment.focus();
+
+ expect(document.activeElement === element).to.be.false;
+
+ const monthSegment = editableSegments.getByType(SegmentTypes.Month);
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(monthSegment),
+ });
+ await sendKeys({ press: 'Tab' });
+ await sendKeys({ press: 'Shift+Tab' });
+
+ expect(document.activeElement === element).to.be.false;
+ });
+
+ it('should not open the calendar', async () => {
+ const calendarButton = element.shadowRoot!.querySelector(
+ 'sp-picker-button'
+ ) as PickerButton;
+
+ await sendMouse({
+ type: 'click',
+ position: getElementCenter(calendarButton),
+ });
+
+ expect(isCalendarOpen(element)).to.be.false;
+ });
});
describe('Localization', () => {
diff --git a/packages/date-time-picker/test/helpers.ts b/packages/date-time-picker/test/helpers.ts
index 1f05fdb2de..d089ce3ac0 100644
--- a/packages/date-time-picker/test/helpers.ts
+++ b/packages/date-time-picker/test/helpers.ts
@@ -24,6 +24,7 @@ import {
SegmentPlaceholders,
} from '@spectrum-web-components/date-time-picker';
import '@spectrum-web-components/date-time-picker/sp-date-time-picker.js';
+import { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js';
import '@spectrum-web-components/theme/sp-theme.js';
import { sendKeys } from '@web/test-runner-commands';
import { spreadProps } from '../../../test/lit-helpers.js';
@@ -56,6 +57,11 @@ export async function fixtureElement({
return el;
}
+/**
+ * Returns an array of all editable segments in the DateTimePicker
+ * with a `getByType` method to find a segment by type.
+ * @param element - The DateTimePicker to get the segments from
+ */
export function getEditableSegments(element: DateTimePicker): EditableSegments {
const elements = Array.from(
element.shadowRoot.querySelectorAll('.editable-segment')
@@ -71,6 +77,73 @@ export function getEditableSegments(element: DateTimePicker): EditableSegments {
return elements as EditableSegments;
}
+/**
+ * Sends the specified key the given number of times.
+ * @param key - The key to send
+ * @param times - The number of times to send the key
+ */
+export function sendKeyMultipleTimes(
+ key: string,
+ times: number
+): Promise {
+ return Promise.all(
+ Array.from({ length: times }).map(() => sendKeys({ press: key }))
+ );
+}
+
+/**
+ * Opens the Calendar by focusing the Calendar button and pressing Enter.
+ * @param element - The DateTimePicker with the Calendar to open
+ */
+export async function openCalendar(element: DateTimePicker): Promise {
+ const calendarButton = element.shadowRoot!.querySelector(
+ 'sp-picker-button'
+ ) as HTMLElement;
+
+ const opened = oneEvent(element, 'sp-opened');
+ calendarButton.focus();
+ await sendKeys({ press: 'Enter' });
+ await opened;
+}
+
+/**
+ * Simulates a date selection in the Calendar by dispatching a change event with the given date.
+ * @param element - The DateTimePicker with the Calendar to dispatch the event on
+ * @param date - The date to set the Calendar to
+ */
+export async function dispatchCalendarChange(
+ element: DateTimePicker,
+ date: DateValue
+): Promise {
+ const calendarEl = element.shadowRoot!.querySelector(
+ 'sp-calendar'
+ ) as Calendar;
+
+ calendarEl.value = date;
+ calendarEl.dispatchEvent(
+ new CustomEvent('change', { bubbles: true, composed: true })
+ );
+ await elementUpdated(element);
+}
+
+/**
+ * Computes the x and y coordinates of the center of the given element, rounded to the nearest integer.
+ * @param element - The element to get the center of
+ * @returns - The x and y coordinates of the center of the element
+ */
+export function getElementCenter(element: HTMLElement): [number, number] {
+ const rect = element.getBoundingClientRect();
+ return [
+ Math.round(rect.left + rect.width / 2),
+ Math.round(rect.top + rect.height / 2),
+ ];
+}
+
+/**
+ * Asserts that the given editable segments have only placeholders.
+ * @param editableSegments - The segments to check
+ * @param exceptions - Segments that are allowed to have content
+ */
export function expectPlaceholders(
editableSegments: EditableSegments,
exceptions: HTMLElement[] = []
@@ -81,6 +154,10 @@ export function expectPlaceholders(
expectPlaceholder(segment);
}
+/**
+ * Asserts that the given segment is a placeholder and does not have a value.
+ * @param segment - The segment to check
+ */
export function expectPlaceholder(segment: HTMLElement): void {
expect(isPlaceholderSegment(segment)).to.be.true;
}
@@ -94,15 +171,12 @@ function isPlaceholderSegment(segment: HTMLElement): boolean {
return true;
}
-export function sendKeyMultipleTimes(
- key: string,
- times: number
-): Promise {
- return Promise.all(
- Array.from({ length: times }).map(() => sendKeys({ press: key }))
- );
-}
-
+/**
+ * Asserts that the given date values are the same day.
+ * @param a - The first date value
+ * @param b - The second date value
+ * @param message - The message to display if the assertion fails
+ */
export function expectSameDates(
a: DateValue,
b: DateValue,
@@ -111,28 +185,22 @@ export function expectSameDates(
expect(isSameDay(a, b), message).to.be.true;
}
-export async function dispatchCalendarChange(
- element: DateTimePicker,
- date: DateValue
-): Promise {
- const calendarEl = element.shadowRoot!.querySelector(
- 'sp-calendar'
- ) as Calendar;
-
- calendarEl.value = date;
- calendarEl.dispatchEvent(
- new CustomEvent('change', { bubbles: true, composed: true })
- );
- await elementUpdated(element);
+/**
+ * Asserts that the given element is focused.
+ * @param rootEl - The document or shadow root to check the active element of
+ * @param focusedEl - The element that should be focused
+ * @param message - The message to display if the assertion fails
+ */
+export function expectFocused(
+ rootEl: Document | ShadowRoot,
+ focusedEl: HTMLElement,
+ message?: string
+): void {
+ expect(rootEl.activeElement === focusedEl, message).to.be.true;
}
-export async function openCalendar(element: DateTimePicker): Promise {
- const calendarButton = element.shadowRoot!.querySelector(
- 'sp-picker-button'
- ) as HTMLElement;
-
- const opened = oneEvent(element, 'sp-opened');
- calendarButton.focus();
- await sendKeys({ press: 'Enter' });
- await opened;
+export function isCalendarOpen(element: DateTimePicker): boolean {
+ const calendar = element.shadowRoot.querySelector('sp-calendar');
+ const calendarOverlay = calendar?.closest('sp-overlay') as Overlay;
+ return calendarOverlay.open;
}
diff --git a/yarn.lock b/yarn.lock
index 290538b0e7..7233c96bb0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5365,168 +5365,9 @@
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-9.0.8.tgz#6af3bcdace903b8461f5fcd4c9aa23e70128a456"
integrity sha512-rGfd7jqXOdR69bEjrRP58ynuIeJU0czPfwQvzhtCzg7jKVukV+efNHqrs086sC6xutB3W4TF71K/dZMr3oyTyg==
-"@spectrum-web-components/action-button@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/action-button/-/action-button-0.47.2.tgz#8db417bfbb4d9e77b7d7ff6a6ad311764c93f7e8"
- integrity sha512-29hiRHxokHzVMqAjX8QeR3iP5MVjJXxPp7AG9x4Sz7yidGeXCHbItdkA/8ADpDVoULEUxQoyp5alqBl5b8ZrMQ==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/button" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
- "@spectrum-web-components/icons-ui" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
-
-"@spectrum-web-components/base@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/base/-/base-0.47.2.tgz#83ce0fbd6a5f29757445224f56f34eb85860ca31"
- integrity sha512-jWktMJUfFIFkV0Q/rC6kx/O4zuTLKSubuPk2dRlucilBxGCUzBFOtdQLYnr4B7nYkukNvBdBloVr2be0bRT0jg==
- dependencies:
- lit "^2.5.0 || ^3.1.3"
-
-"@spectrum-web-components/button@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/button/-/button-0.47.2.tgz#cebf73bbc7edcff68d7966bd3fb52dc011186206"
- integrity sha512-y0IV0zaPTC4z24RYaZcXy8apzXWIjKQm9aPQdYmeHWwwT/8PDEC83hSbaQXQcVLXpeQJZCPXYNTX5d/5j4a0cg==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/clear-button" "^0.47.2"
- "@spectrum-web-components/close-button" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
- "@spectrum-web-components/icons-ui" "^0.47.2"
- "@spectrum-web-components/progress-circle" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
-
-"@spectrum-web-components/clear-button@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/clear-button/-/clear-button-0.47.2.tgz#60aa43d8fc1b5b0c9ba79391a233785febde5efc"
- integrity sha512-Yhf8aRmHtXnlGM6eUFdq4eoi/P4zuI3NVA8uQLA8m99waYv9/TdKvDe2QSuwI4GyFcKnUTBnvmq3Rs6rrw/YcA==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
-
-"@spectrum-web-components/close-button@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/close-button/-/close-button-0.47.2.tgz#dfcba19d0d7d40dc277d20f672a6eac85cc7fc91"
- integrity sha512-EbaxJmK+RqYPZ3wk46urj9bptUX5UvusVWcWaVV6rTI5x+tvcIIJYaCIo2faBijEqteGN1q+HDGgi09448WCNA==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
-
"@spectrum-web-components/eslint-plugin@file:./linters/eslint":
version "0.49.0"
-"@spectrum-web-components/field-label@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/field-label/-/field-label-0.47.2.tgz#52c8b6c85fa03c9fdd7a1ecbbf5b391137afa734"
- integrity sha512-oosf4eQ+oisrzO08rB9HR+7zpsqV1EjfUSLLfIPbfe59wY69DzKMidZcHVIR3TzrTs5HnW3JTbXRqycgBCMSug==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
- "@spectrum-web-components/icons-ui" "^0.47.2"
- "@spectrum-web-components/reactive-controllers" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
-
-"@spectrum-web-components/icon@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/icon/-/icon-0.47.2.tgz#24ab873ecf3a48f5d5f93e5e59362bd75cd5ff9a"
- integrity sha512-BSgFtfHhvmfjdEG7ANzG7YpwiRttefRfgeYsa/8xK9QuH7Mu1MA8BPhDlHIIqaMq9/8CaYY6JxhAJxnlvnlxpA==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/iconset" "^0.47.2"
-
-"@spectrum-web-components/icons-ui@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/icons-ui/-/icons-ui-0.47.2.tgz#cb38e173327da890daa87132a44016a4ebacb260"
- integrity sha512-LOKUGU/Es6gMq5+/1ci7azVSVv76hqlmap/Vg+vxrFygr+Ne7g0D6jd4ICz7i5/WCYG7j5wvgS7Xs5l0epUxGg==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
- "@spectrum-web-components/iconset" "^0.47.2"
-
-"@spectrum-web-components/icons-workflow@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/icons-workflow/-/icons-workflow-0.47.2.tgz#77b6132478182ab7aea790c2db9cfb086966af17"
- integrity sha512-jpGcKKHcXrLf7HarBoV/iXYBfT7K8HYgFsYM/qSN0IynsaLMkplGX1bubRCnfACYszTzEXGw2+9yCpzblknG5Q==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
-
-"@spectrum-web-components/iconset@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/iconset/-/iconset-0.47.2.tgz#a4b438f3d37dc303bcb8eb8182ba6d265548295d"
- integrity sha512-fLYj5Xb1rhpCqOgLsR4d776F48i2zW0s0j2L78pMKVDqipmFI4rQqX+Zgg7n4nhfTzzYZH1K6mr8/xQvjVd7Fg==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
-
-"@spectrum-web-components/overlay@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/overlay/-/overlay-0.47.2.tgz#357a35c79fb3936af81ecc84d106d3a0ce79e3d7"
- integrity sha512-JSwgRfWOblCC2UB5/QaLvBD0peNJOp4ZVKG8mBHHsdC0U4Pudk67pzbNcJPq77MuoSKvG/MF5JIaUfxnQ7XbPg==
- dependencies:
- "@floating-ui/dom" "^1.6.1"
- "@floating-ui/utils" "^0.2.1"
- "@spectrum-web-components/action-button" "^0.47.2"
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/reactive-controllers" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
- "@spectrum-web-components/theme" "^0.47.2"
-
-"@spectrum-web-components/picker-button@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/picker-button/-/picker-button-0.47.2.tgz#30e1b3f448567f60aa0e7efa67ec39f034071064"
- integrity sha512-jzoUW5xbyk5EnHON6gjAZMkYFZ9Du5vezGmfozAOgENXQDV/pcNr3z40Hd2U321zYJaUr3ya98rt76kUuot7+g==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/button" "^0.47.2"
- "@spectrum-web-components/icon" "^0.47.2"
- "@spectrum-web-components/icons-ui" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
-
-"@spectrum-web-components/popover@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/popover/-/popover-0.47.2.tgz#691530d3edf659cada6a72d02e87c5ee890922af"
- integrity sha512-dHxK8rje6NoA0FEDl06vcA/HNpn+le8Qk6l2YFJx+mg0Zup4fYHYPHNXVJMeSEKPBnVr8J8LLg9TXxb8Ukk2aw==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/overlay" "^0.47.2"
-
-"@spectrum-web-components/progress-circle@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/progress-circle/-/progress-circle-0.47.2.tgz#b41446fdb8af91b18b007069886a9ef60e960f25"
- integrity sha512-sp5yv7Bt0kTs9Vpip31MKlnJTdfi4A1xOyyjz+haXAVWXSIBp7q+y2/KXLP5sToaTDGRzFOx9c8X7ZNt/OHOTA==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/shared" "^0.47.2"
-
-"@spectrum-web-components/reactive-controllers@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/reactive-controllers/-/reactive-controllers-0.47.2.tgz#87eebd9d08b769bfaee2347823087f74ff41fe38"
- integrity sha512-rJ6tWx7LNWmz1vjup3ozzcR+XowIu8HBuD5KiwkHsYuTFA1pK/2iIMaJl4FMNsG2X6EUM2U114atasy01pnJbA==
- dependencies:
- lit "^3.1.3"
-
-"@spectrum-web-components/shared@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/shared/-/shared-0.47.2.tgz#b535954898209f35b5cf2454f1168938eb1094e4"
- integrity sha512-S7WU7AS+iw4p9TUZgl2JF/KEB5c2FfjNloYdTPP+4kRC0HiZ57Wj7vSPrL4a7Kt8ucIwHqzm23GwaTcwQckMXQ==
- dependencies:
- "@lit-labs/observers" "^2.0.2"
- "@spectrum-web-components/base" "^0.47.2"
- focus-visible "^5.1.0"
-
-"@spectrum-web-components/styles@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/styles/-/styles-0.47.2.tgz#cf6d6cd3fb67711470f77160dd8f82099f68bd1b"
- integrity sha512-l3b9srYFQlwKWXTZ9DPDPcVqu2fQVQEdP0flzaLXm0zn5lIpWz0VIzNphV3jFsTFXsZcjZzwFsLWGFzfz6geJw==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
-
-"@spectrum-web-components/theme@^0.47.2":
- version "0.47.2"
- resolved "https://registry.yarnpkg.com/@spectrum-web-components/theme/-/theme-0.47.2.tgz#1e82db4dcd6f9601fa3082e64e1dbe15f711f3ed"
- integrity sha512-bK+sTtHLxakgDNI3yV0R6lnVTjR3rpa09y+8Wq6qX4+xFOVxWHw+1RP6/3M1JxUM7SK0Qw8jhUR7aIiZ36tR6A==
- dependencies:
- "@spectrum-web-components/base" "^0.47.2"
- "@spectrum-web-components/styles" "^0.47.2"
-
"@storybook/addon-a11y@^7.5.0":
version "7.6.19"
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-7.6.19.tgz#8b684edfb3a387d24398e81517dc734f0b76a1db"