diff --git a/libs/components/project.json b/libs/components/project.json index f5cdc32d86..9d69007479 100644 --- a/libs/components/project.json +++ b/libs/components/project.json @@ -59,10 +59,9 @@ } }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/vite:test", "outputs": ["{workspaceRoot}/coverage/libs/components"], "options": { - "jestConfig": "libs/components/jest.config.cjs", "passWithNoTests": true } }, diff --git a/libs/components/src/lib/alert/alert.spec.ts b/libs/components/src/lib/alert/alert.spec.ts index 845941b61e..8f42e433b9 100644 --- a/libs/components/src/lib/alert/alert.spec.ts +++ b/libs/components/src/lib/alert/alert.spec.ts @@ -97,7 +97,7 @@ describe('vwc-alert', () => { describe('focus', () => { it('should focus when opened', async () => { - const spy = jest.fn(); + const spy = vi.fn(); const alertText: HTMLElement = element.shadowRoot?.querySelector( '.alert-text' ) as HTMLElement; @@ -145,17 +145,17 @@ describe('vwc-alert', () => { describe('timeoutms', function () { it('should fire close event after timeoutms milliseconds', async function () { - jest.useFakeTimers(); - const spy = jest.fn(); + vi.useFakeTimers(); + const spy = vi.fn(); element.timeoutms = 100; element.open = true; element.addEventListener('close', spy); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); expect(spy).toHaveBeenCalled(); - jest.useRealTimers(); + vi.useRealTimers(); }); }); @@ -312,7 +312,7 @@ describe('vwc-alert', () => { beforeEach(() => (element.open = true)); it('should remove the alert when esc and removable is true', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.removable = true; element.addEventListener('close', spy); @@ -324,7 +324,7 @@ describe('vwc-alert', () => { }); it('should remove the alert only on escape key', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.removable = true; element.addEventListener('close', spy); @@ -336,7 +336,7 @@ describe('vwc-alert', () => { }); it('should remove keydown listener after disconnection', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.removable = true; element.addEventListener('close', spy); @@ -348,7 +348,7 @@ describe('vwc-alert', () => { }); it('should not fire close event when removable is false', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.removable = false; element.addEventListener('close', spy); @@ -359,7 +359,7 @@ describe('vwc-alert', () => { }); it('should stop propgation on escape key', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); getBaseElement(element).dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape' }) @@ -370,7 +370,7 @@ describe('vwc-alert', () => { it('should enable default if Escape was pressed', async () => { const event = new KeyboardEvent('keydown', { key: 'Escape' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); @@ -378,7 +378,7 @@ describe('vwc-alert', () => { it('should enable default if key is not Escape', async () => { const event = new KeyboardEvent('keydown', { key: ' ' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); diff --git a/libs/components/src/lib/audio-player/audio-player.spec.ts b/libs/components/src/lib/audio-player/audio-player.spec.ts index 2611d19e41..3851dc04e0 100644 --- a/libs/components/src/lib/audio-player/audio-player.spec.ts +++ b/libs/components/src/lib/audio-player/audio-player.spec.ts @@ -27,7 +27,7 @@ describe('vwc-audio-player', () => { } function setAudioElementDuration(duration: number) { - jest.spyOn(nativeAudioElement, 'duration', 'get').mockReturnValue(duration); + vi.spyOn(nativeAudioElement, 'duration', 'get').mockReturnValue(duration); } function setAudioElementCurrentTime(time: number) { @@ -101,14 +101,14 @@ describe('vwc-audio-player', () => { `<${COMPONENT_TAG} timestamp src="${SOURCE}">` )) as AudioPlayer; - jest.spyOn(nativeAudioElement, 'play').mockImplementation(() => { + vi.spyOn(nativeAudioElement, 'play').mockImplementation(() => { return new Promise((res) => { - jest.spyOn(nativeAudioElement, 'paused', 'get').mockReturnValue(false); + vi.spyOn(nativeAudioElement, 'paused', 'get').mockReturnValue(false); res(); }); }); - jest.spyOn(nativeAudioElement, 'pause').mockImplementation(async () => { - jest.spyOn(nativeAudioElement, 'paused', 'get').mockReturnValue(true); + vi.spyOn(nativeAudioElement, 'pause').mockImplementation(async () => { + vi.spyOn(nativeAudioElement, 'paused', 'get').mockReturnValue(true); }); pauseButton = getPauseButtonElement(); @@ -365,7 +365,7 @@ describe('vwc-audio-player', () => { dragSliderTo(25); await elementUpdated(element); - const playSpy = jest.spyOn(element, 'play'); + const playSpy = vi.spyOn(element, 'play'); stopSliderDrag(); getSliderElement().value = '20'; await elementUpdated(element); @@ -410,7 +410,7 @@ describe('vwc-audio-player', () => { setCurrentTimeAndDuration(10, duration); await elementUpdated(element); - const pauseSpy = jest.spyOn(element, 'pause'); + const pauseSpy = vi.spyOn(element, 'pause'); dragSliderTo(20); await elementUpdated(element); diff --git a/libs/components/src/lib/banner/banner.spec.ts b/libs/components/src/lib/banner/banner.spec.ts index 6b1c414a51..61066c508f 100644 --- a/libs/components/src/lib/banner/banner.spec.ts +++ b/libs/components/src/lib/banner/banner.spec.ts @@ -93,14 +93,14 @@ describe('vwc-banner', () => { describe('remove', function () { it('should fire removing event', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('removing', spy); element.remove(); expect(spy).toHaveBeenCalled(); }); it('should fire removed after animation end', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('removed', spy); element.remove(); @@ -111,7 +111,7 @@ describe('vwc-banner', () => { }); it('should disable removed and removing events after disconnected callback', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('removed', spy); element.addEventListener('removing', spy); element.disconnectedCallback(); @@ -280,7 +280,7 @@ describe('vwc-banner', () => { it('should remove the button on escape key', async function () { element.removable = true; element.focus(); - jest.spyOn(element, 'remove'); + vi.spyOn(element, 'remove'); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(element.remove).toHaveBeenCalled(); }); @@ -288,7 +288,7 @@ describe('vwc-banner', () => { it('should remove the banner only on escape key', async function () { element.removable = true; element.focus(); - const spy = jest.spyOn(element, 'remove'); + const spy = vi.spyOn(element, 'remove'); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); expect((spy as any).mock.calls.length).toEqual(0); }); @@ -297,7 +297,7 @@ describe('vwc-banner', () => { element.removable = true; element.focus(); element.disconnectedCallback(); - const spy = jest.spyOn(element, 'remove'); + const spy = vi.spyOn(element, 'remove'); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect((spy as any).mock.calls.length).toEqual(0); }); @@ -305,7 +305,7 @@ describe('vwc-banner', () => { it('should remove the banner only if "removable" is true', async function () { element.removable = false; element.focus(); - const spy = jest.spyOn(element, 'remove'); + const spy = vi.spyOn(element, 'remove'); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect((spy as any).mock.calls.length).toEqual(0); }); diff --git a/libs/components/src/lib/calendar/calendar.spec.ts b/libs/components/src/lib/calendar/calendar.spec.ts index 2155038818..d39ae6c06e 100644 --- a/libs/components/src/lib/calendar/calendar.spec.ts +++ b/libs/components/src/lib/calendar/calendar.spec.ts @@ -121,8 +121,8 @@ describe('vwc-calendar', () => { it('should return correct day and hour from mouse clicking inside one of the columns cells', async () => { const e = new MouseEvent('click', { composed: true, clientY: 54 }); - e.composedPath = jest.fn().mockReturnValue([gridCell]); - gridCell.getBoundingClientRect = jest + e.composedPath = vi.fn().mockReturnValue([gridCell]); + gridCell.getBoundingClientRect = vi .fn() .mockReturnValue({ height: 1175, y: 28 }); @@ -136,7 +136,7 @@ describe('vwc-calendar', () => { const rowHeader = element.shadowRoot?.querySelector( '[role="rowheader"]:nth-child(3)' ) as HTMLElement; - rowHeader.getBoundingClientRect = jest + rowHeader.getBoundingClientRect = vi .fn() .mockReturnValue({ height: 49, y: 85 }); @@ -149,7 +149,7 @@ describe('vwc-calendar', () => { clientX: 25, clientY: 174, }); - e.composedPath = jest.fn().mockReturnValue([rowHeaderTimeElement]); + e.composedPath = vi.fn().mockReturnValue([rowHeaderTimeElement]); context = element.getEventContext(e); @@ -167,7 +167,7 @@ describe('vwc-calendar', () => { clientX: 0, clientY: 0, }); - e.composedPath = jest.fn().mockReturnValue([grid]); + e.composedPath = vi.fn().mockReturnValue([grid]); context = element.getEventContext(e); @@ -176,7 +176,7 @@ describe('vwc-calendar', () => { it('should throw if unsupported event passed', async () => { const e = new FocusEvent('focus'); - e.composedPath = jest.fn().mockReturnValue([gridCell]); + e.composedPath = vi.fn().mockReturnValue([gridCell]); const getEventContext = () => element.getEventContext(e as MouseEvent); @@ -187,7 +187,7 @@ describe('vwc-calendar', () => { it('should throw if event is missing a target', async () => { const e = new MouseEvent('click', { composed: true, clientY: 54 }); - gridCell.getBoundingClientRect = jest + gridCell.getBoundingClientRect = vi .fn() .mockReturnValue({ height: 1175, y: 28 }); @@ -364,7 +364,7 @@ describe('vwc-calendar', () => { /* skipped because "Certain ARIA roles must contain particular children (aria-required-children)" */ describe('a11y', () => { - xit('should pass html a11y test', async () => { + it.skip('should pass html a11y test', async () => { expect(await axe(element)).toHaveNoViolations(); }); }); diff --git a/libs/components/src/lib/checkbox/checkbox.spec.ts b/libs/components/src/lib/checkbox/checkbox.spec.ts index 4dfdaef210..38b2478755 100644 --- a/libs/components/src/lib/checkbox/checkbox.spec.ts +++ b/libs/components/src/lib/checkbox/checkbox.spec.ts @@ -287,7 +287,7 @@ describe('vwc-checkbox', () => { describe.each(['input', 'change'])('%s event', (eventName) => { it('should be fired when a user toggles the checkbox', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); getBaseElement(element).click(); diff --git a/libs/components/src/lib/combobox/combobox.spec.ts b/libs/components/src/lib/combobox/combobox.spec.ts index 0f047562d4..f10512881e 100644 --- a/libs/components/src/lib/combobox/combobox.spec.ts +++ b/libs/components/src/lib/combobox/combobox.spec.ts @@ -44,7 +44,7 @@ describe('vwc-combobox', () => { }; beforeAll(() => { - HTMLElement.prototype.scrollIntoView = jest.fn(); + HTMLElement.prototype.scrollIntoView = vi.fn(); }); beforeEach(async () => { @@ -146,7 +146,7 @@ describe('vwc-combobox', () => { }); it('should allow propgation on escape key if not open', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); element.dispatchEvent( @@ -162,7 +162,7 @@ describe('vwc-combobox', () => { it('should stop propgation on escape key if open', async () => { element.open = true; - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); element.dispatchEvent( @@ -547,7 +547,7 @@ describe('vwc-combobox', () => { it('should close and select the first matching option when pressing Enter when autocomplete is %s', async () => { element.open = true; await elementUpdated(element); - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('change', spy); typeInput('ana'); @@ -628,7 +628,7 @@ describe('vwc-combobox', () => { }); describe('when an option is selected', () => { - let changeSpy: jest.Mock; + let changeSpy: vi.Mock; beforeEach(async () => { element.innerHTML = ` @@ -636,7 +636,7 @@ describe('vwc-combobox', () => { `; element.open = true; await elementUpdated(element); - changeSpy = jest.fn(); + changeSpy = vi.fn(); element.addEventListener('change', changeSpy); getOption('Apple').click(); }); diff --git a/libs/components/src/lib/data-grid/data-grid-cell.spec.ts b/libs/components/src/lib/data-grid/data-grid-cell.spec.ts index b40770ffe6..de198f301d 100644 --- a/libs/components/src/lib/data-grid/data-grid-cell.spec.ts +++ b/libs/components/src/lib/data-grid/data-grid-cell.spec.ts @@ -175,7 +175,7 @@ describe('vwc-data-grid-cell', () => { }); it('should fire "cell-focused" event', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('cell-focused', spy); element.focus(); expect(spy).toHaveBeenCalledTimes(1); @@ -191,7 +191,7 @@ describe('vwc-data-grid-cell', () => { }); it('should ignore additional focusin events', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('cell-focused', spy); element.dispatchEvent(new Event('focusin')); @@ -369,12 +369,12 @@ describe('vwc-data-grid-cell', () => { }); describe('sort event', () => { - let onSortSpy: jest.Mock; + let onSortSpy: vi.Mock; beforeEach(async () => { element.cellType = 'columnheader'; element.innerHTML = 'Name'; await elementUpdated(element); - onSortSpy = jest.fn(); + onSortSpy = vi.fn(); element.addEventListener('sort', onSortSpy); }); @@ -445,13 +445,13 @@ describe('vwc-data-grid-cell', () => { }); describe('cell-click event', () => { - let onCellClickSpy: jest.Mock; + let onCellClickSpy: vi.Mock; let expectedDetail: object; beforeEach(async () => { element.cellType = 'default'; element.innerHTML = 'Name'; await elementUpdated(element); - onCellClickSpy = jest.fn(); + onCellClickSpy = vi.fn(); element.addEventListener('cell-click', onCellClickSpy); expectedDetail = { cell: element, diff --git a/libs/components/src/lib/data-grid/data-grid-row.spec.ts b/libs/components/src/lib/data-grid/data-grid-row.spec.ts index 8c29db836f..ad7485adb2 100644 --- a/libs/components/src/lib/data-grid/data-grid-row.spec.ts +++ b/libs/components/src/lib/data-grid/data-grid-row.spec.ts @@ -138,7 +138,7 @@ describe('vwc-data-grid-row', () => { describe('row-focused event', () => { it('should fire the focused event when one of the cells is focused', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('row-focused', spy); element.dispatchEvent(new FocusEvent('cell-focused')); expect(spy).toHaveBeenCalled(); @@ -267,15 +267,17 @@ describe('vwc-data-grid-row', () => { describe('a11y', () => { it('should pass html a11y test', async () => { - element = (await fixture(` + const element = (await fixture(`
<${COMPONENT_TAG}>
- `)) as DataGridRow; - element.ariaSelected = 'true'; - await elementUpdated(element); + `)) as HTMLDivElement; + const row = element.querySelector('vwc-data-grid-row') as DataGridRow; + row.ariaSelected = 'true'; + + await elementUpdated(row); expect(await axe(element)).toHaveNoViolations(); }); diff --git a/libs/components/src/lib/data-grid/data-grid.spec.ts b/libs/components/src/lib/data-grid/data-grid.spec.ts index dad615376c..b7a7ab6629 100644 --- a/libs/components/src/lib/data-grid/data-grid.spec.ts +++ b/libs/components/src/lib/data-grid/data-grid.spec.ts @@ -6,7 +6,7 @@ import { DataGridRow } from './data-grid-row.ts'; const COMPONENT_TAG = 'vwc-data-grid'; -Element.prototype.scrollIntoView = jest.fn(); +Element.prototype.scrollIntoView = vi.fn(); function setMockRows(element: DataGrid) { element.rowElementTag = 'div'; @@ -23,6 +23,8 @@ describe('vwc-data-grid', () => { element = (await fixture( `<${COMPONENT_TAG}>` )) as DataGrid; + + await elementUpdated(element); }); describe('basic', () => { diff --git a/libs/components/src/lib/data-grid/data-grid.ts b/libs/components/src/lib/data-grid/data-grid.ts index c3f63c3366..43bc962912 100644 --- a/libs/components/src/lib/data-grid/data-grid.ts +++ b/libs/components/src/lib/data-grid/data-grid.ts @@ -643,12 +643,9 @@ export class DataGrid extends VividElement { this.generateHeader === GenerateHeaderOptions.sticky ? DataGridRowTypes.stickyHeader : DataGridRowTypes.header; - /* istanbul ignore next */ - if (this.firstChild !== null || this.rowsPlaceholder !== null) { - this.insertBefore( - generatedHeaderElement, - this.firstChild !== null ? this.firstChild : this.rowsPlaceholder - ); + + if (this.firstChild !== null) { + this.insertBefore(generatedHeaderElement, this.firstChild); } return; } diff --git a/libs/components/src/lib/data-grid/index.spec.ts b/libs/components/src/lib/data-grid/index.spec.ts index bd4390158d..e118ca3578 100644 --- a/libs/components/src/lib/data-grid/index.spec.ts +++ b/libs/components/src/lib/data-grid/index.spec.ts @@ -90,7 +90,7 @@ describe('data grid integration tests', () => { describe('events', function () { it('should fire cell-focused event', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('cell-focused', spy); element.rowsData = [ @@ -103,7 +103,7 @@ describe('data grid integration tests', () => { }); it('should fire row-focused event', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('row-focused', spy); element.rowsData = [ @@ -131,7 +131,7 @@ describe('data grid integration tests', () => { } it('should fire sort event', async function () { await addSortableHeader(); - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('sort', spy); (element.rowElements[0].children[1] as HTMLElement).click(); @@ -145,8 +145,8 @@ describe('data grid integration tests', () => { it('should stop sort event propagation', async function () { await addSortableHeader(); - const elementSpy = jest.fn(); - const parentSpy = jest.fn(); + const elementSpy = vi.fn(); + const parentSpy = vi.fn(); element.addEventListener('sort', elementSpy); element.parentElement!.addEventListener('sort', parentSpy); diff --git a/libs/components/src/lib/date-picker/date-picker.a11y.spec.ts b/libs/components/src/lib/date-picker/date-picker.a11y.spec.ts new file mode 100644 index 0000000000..2d2a6581a2 --- /dev/null +++ b/libs/components/src/lib/date-picker/date-picker.a11y.spec.ts @@ -0,0 +1,31 @@ +import { + axe, + elementUpdated, + fixture, + setupDelegatesFocusPolyfill, +} from '@vivid-nx/shared'; +import { DatePicker } from './date-picker'; +import '.'; + +const COMPONENT_TAG = 'vwc-date-picker'; + +describe('vwc-date-picker', () => { + let element: DatePicker; + + beforeEach(async () => { + element = (await fixture( + `<${COMPONENT_TAG}>` + )) as DatePicker; + + setupDelegatesFocusPolyfill(element); + }); + + describe('a11y', () => { + it('should pass html a11y test', async () => { + element.value = '2012-12-12'; + await elementUpdated(element); + + expect(await axe(element)).toHaveNoViolations(); + }, 10000); + }); +}); diff --git a/libs/components/src/lib/date-picker/date-picker.spec.ts b/libs/components/src/lib/date-picker/date-picker.spec.ts index d53325b2f5..eb7109605e 100644 --- a/libs/components/src/lib/date-picker/date-picker.spec.ts +++ b/libs/components/src/lib/date-picker/date-picker.spec.ts @@ -1,5 +1,4 @@ import { - axe, createFormHTML, elementUpdated, fixture, @@ -17,14 +16,14 @@ const COMPONENT_TAG = 'vwc-date-picker'; // Mock current date to be 2023-08-10 for the tests -jest.mock('../../shared/date-picker/calendar/month.ts', () => ({ - ...jest.requireActual('../../shared/date-picker/calendar/month.ts'), - getCurrentMonth: jest.fn().mockReturnValue({ month: 7, year: 2023 }), +vi.mock('../../shared/date-picker/calendar/month.ts', async () => ({ + ...(await vi.importActual('../../shared/date-picker/calendar/month.ts')), + getCurrentMonth: vi.fn().mockReturnValue({ month: 7, year: 2023 }), })); -jest.mock('../../shared/date-picker/calendar/dateStr.ts', () => ({ - ...jest.requireActual('../../shared/date-picker/calendar/dateStr.ts'), - currentDateStr: jest.fn().mockReturnValue('2023-08-10'), +vi.mock('../../shared/date-picker/calendar/dateStr.ts', async () => ({ + ...(await vi.importActual('../../shared/date-picker/calendar/dateStr.ts')), + currentDateStr: vi.fn().mockReturnValue('2023-08-10'), })); describe('vwc-date-picker', () => { @@ -39,9 +38,8 @@ describe('vwc-date-picker', () => { ) as HTMLButtonElement; const getButtonByLabel = (label: string) => - element.shadowRoot!.querySelector( - `[aria-label="${label}"],[label="${label}"]` - ) as Button; + (element.shadowRoot!.querySelector(`[aria-label="${label}"]`) ?? + element.shadowRoot!.querySelector(`[label="${label}"]`)) as Button; const getDialogTitle = () => titleAction.textContent!.trim(); @@ -151,7 +149,7 @@ describe('vwc-date-picker', () => { describe.each(['input', 'change'])('%s event', (eventName) => { it('should be fired when a user enters a valid date into the text field', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); typeIntoTextField('01/21/2021'); @@ -161,7 +159,7 @@ describe('vwc-date-picker', () => { }); it('should be fired when a user clicks on a date in the calendar', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); await openPopup(); @@ -203,7 +201,7 @@ describe('vwc-date-picker', () => { it('should keep default behaviour when pressing tab in the text-field without a tabbable date', async () => { const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); element.min = '2023-12-31'; element.value = '2023-01-01'; await openPopup(); @@ -215,7 +213,7 @@ describe('vwc-date-picker', () => { it('should keep default behaviour when pressing tab in the text-field without a tabbable month', async () => { const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); element.min = '2024-01-01'; element.value = '2023-01-01'; await openPopup(); @@ -340,13 +338,4 @@ describe('vwc-date-picker', () => { expect(textField.currentValue).toBe('21.01.2021'); }); }); - - describe('a11y', () => { - it('should pass html a11y test', async () => { - element.value = '2012-12-12'; - await elementUpdated(element); - - expect(await axe(element)).toHaveNoViolations(); - }); - }); }); diff --git a/libs/components/src/lib/date-range-picker/date-range-picker.a11y.spec.ts b/libs/components/src/lib/date-range-picker/date-range-picker.a11y.spec.ts new file mode 100644 index 0000000000..6f0278183d --- /dev/null +++ b/libs/components/src/lib/date-range-picker/date-range-picker.a11y.spec.ts @@ -0,0 +1,31 @@ +import { + axe, + elementUpdated, + fixture, + setupDelegatesFocusPolyfill, +} from '@vivid-nx/shared'; +import { DateRangePicker } from './date-range-picker'; +import '.'; + +const COMPONENT_TAG = 'vwc-date-range-picker'; + +describe('vwc-date-range-picker', () => { + let element: DateRangePicker; + + beforeEach(async () => { + element = (await fixture( + `<${COMPONENT_TAG}>` + )) as DateRangePicker; + setupDelegatesFocusPolyfill(element); + }); + + describe('a11y', () => { + it('should pass html a11y test', async () => { + element.start = '2012-12-12'; + element.end = '2012-12-13'; + await elementUpdated(element); + + expect(await axe(element)).toHaveNoViolations(); + }, 10000); + }); +}); diff --git a/libs/components/src/lib/date-range-picker/date-range-picker.spec.ts b/libs/components/src/lib/date-range-picker/date-range-picker.spec.ts index 4a4551389e..c42bd5cdeb 100644 --- a/libs/components/src/lib/date-range-picker/date-range-picker.spec.ts +++ b/libs/components/src/lib/date-range-picker/date-range-picker.spec.ts @@ -1,5 +1,4 @@ import { - axe, elementUpdated, fixture, setupDelegatesFocusPolyfill, @@ -17,14 +16,14 @@ const COMPONENT_TAG = 'vwc-date-range-picker'; // Mock current date to be 2023-08-10 for the tests -jest.mock('../../shared/date-picker/calendar/month.ts', () => ({ - ...jest.requireActual('../../shared/date-picker/calendar/month.ts'), - getCurrentMonth: jest.fn().mockReturnValue({ month: 7, year: 2023 }), +vi.mock('../../shared/date-picker/calendar/month.ts', async () => ({ + ...(await vi.importActual('../../shared/date-picker/calendar/month.ts')), + getCurrentMonth: vi.fn().mockReturnValue({ month: 7, year: 2023 }), })); -jest.mock('../../shared/date-picker/calendar/dateStr.ts', () => ({ - ...jest.requireActual('../../shared/date-picker/calendar/dateStr.ts'), - currentDateStr: jest.fn().mockReturnValue('2023-08-10'), +vi.mock('../../shared/date-picker/calendar/dateStr.ts', async () => ({ + ...(await vi.importActual('../../shared/date-picker/calendar/dateStr.ts')), + currentDateStr: vi.fn().mockReturnValue('2023-08-10'), })); describe('vwc-date-range-picker', () => { @@ -45,9 +44,8 @@ describe('vwc-date-range-picker', () => { ) as HTMLButtonElement[]; const getButtonByLabel = (label: string) => - element.shadowRoot!.querySelector( - `[aria-label="${label}"],[label="${label}"]` - ) as Button; + (element.shadowRoot!.querySelector(`[aria-label="${label}"]`) ?? + element.shadowRoot!.querySelector(`[label="${label}"]`)) as Button; const getDialogTitle = () => titleAction.textContent!.trim(); @@ -234,7 +232,7 @@ describe('vwc-date-range-picker', () => { describe.each(['input', 'change'])('%s event', (eventName) => { it('should be fired when a user enters a valid date range into the text field', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); typeIntoTextField('01/21/2021 – 01/22/2021'); @@ -244,7 +242,7 @@ describe('vwc-date-range-picker', () => { }); it('should be fired when a user selects a start date in the calendar', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); await openPopup(); @@ -256,7 +254,7 @@ describe('vwc-date-range-picker', () => { it('should be fired when a user selects an end date in the calendar', async () => { await openPopup(); getDateButton('2023-08-01').click(); - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); getDateButton('2023-08-10').click(); @@ -267,7 +265,7 @@ describe('vwc-date-range-picker', () => { describe('input:start event', () => { it('should be fired when a user enters a valid date range into the text field', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input:start', spy); typeIntoTextField('01/21/2021 – 01/22/2021'); @@ -277,7 +275,7 @@ describe('vwc-date-range-picker', () => { }); it('should be fired when a user select a start date in the calendar', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input:start', spy); await openPopup(); @@ -289,7 +287,7 @@ describe('vwc-date-range-picker', () => { describe('input:end event', () => { it('should be fired when a user enters a valid date range into the text field', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input:end', spy); typeIntoTextField('01/21/2021 – 01/22/2021'); @@ -299,7 +297,7 @@ describe('vwc-date-range-picker', () => { }); it('should be fired when a user select an end date in the calendar', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input:end', spy); await openPopup(); getDateButton('2023-08-01').click(); @@ -350,7 +348,7 @@ describe('vwc-date-range-picker', () => { it('should keep default behaviour when pressing tab in the text-field without a tabbable date', async () => { const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); element.min = '2023-12-31'; element.start = '2023-01-01'; await openPopup(); @@ -362,7 +360,7 @@ describe('vwc-date-range-picker', () => { it('should keep default behaviour when pressing tab in the text-field without a tabbable month', async () => { const event = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); element.min = '2024-01-01'; element.start = '2023-01-01'; await openPopup(); @@ -582,10 +580,10 @@ describe('vwc-date-range-picker', () => { // Cannot properly end-to-end test form value because jsdom does not support ElementInternals // Instead we mock the setFormValue method and test that it is called with the correct value const getFormValue = () => - jest.mocked(element.setFormValue).mock.lastCall![0] as FormData; + vi.mocked(element.setFormValue).mock.lastCall![0] as FormData; beforeEach(() => { - element.setFormValue = jest.fn(); + element.setFormValue = vi.fn(); }); it('should set the form value when name, start and end date are set', () => { @@ -641,14 +639,4 @@ describe('vwc-date-range-picker', () => { expect(textField.value).toBe('21.01.2021 – 22.01.2021'); }); }); - - describe('a11y', () => { - it('should pass html a11y test', async () => { - element.start = '2012-12-12'; - element.end = '2012-12-13'; - await elementUpdated(element); - - expect(await axe(element)).toHaveNoViolations(); - }); - }); }); diff --git a/libs/components/src/lib/dial-pad/dial-pad.spec.ts b/libs/components/src/lib/dial-pad/dial-pad.spec.ts index b57123fe07..5664f50c3a 100644 --- a/libs/components/src/lib/dial-pad/dial-pad.spec.ts +++ b/libs/components/src/lib/dial-pad/dial-pad.spec.ts @@ -82,15 +82,15 @@ describe('vwc-dial-pad', () => { it('should activate number buttons when input event is fired a number for 200ms', async function () { const digitButton = getDigitButtons()[3]; const activeStateBeforeTyping = digitButton.active; - jest.useFakeTimers(); + vi.useFakeTimers(); getTextField().dispatchEvent( new KeyboardEvent('keydown', { key: digitButton.value }) ); const activeStateAfterTyping = digitButton.active; - jest.advanceTimersByTime(200); - jest.useRealTimers(); + vi.advanceTimersByTime(200); + vi.useRealTimers(); expect(activeStateBeforeTyping).toBe(false); expect(activeStateAfterTyping).toBe(true); @@ -100,15 +100,15 @@ describe('vwc-dial-pad', () => { it('should activate * buttons when input event is fired a * for 200ms', async function () { const digitButton = getDigitButtons()[9]; const activeStateBeforeTyping = digitButton.active; - jest.useFakeTimers(); + vi.useFakeTimers(); getTextField().dispatchEvent( new KeyboardEvent('keydown', { key: digitButton.value }) ); const activeStateAfterTyping = digitButton.active; - jest.advanceTimersByTime(200); - jest.useRealTimers(); + vi.advanceTimersByTime(200); + vi.useRealTimers(); expect(activeStateBeforeTyping).toBe(false); expect(activeStateAfterTyping).toBe(true); @@ -118,15 +118,15 @@ describe('vwc-dial-pad', () => { it('should activate # buttons when input event is fired a # for 200ms', async function () { const digitButton = getDigitButtons()[11]; const activeStateBeforeTyping = digitButton.active; - jest.useFakeTimers(); + vi.useFakeTimers(); getTextField().dispatchEvent( new KeyboardEvent('keydown', { key: digitButton.value }) ); const activeStateAfterTyping = digitButton.active; - jest.advanceTimersByTime(200); - jest.useRealTimers(); + vi.advanceTimersByTime(200); + vi.useRealTimers(); expect(activeStateBeforeTyping).toBe(false); expect(activeStateAfterTyping).toBe(true); @@ -173,7 +173,7 @@ describe('vwc-dial-pad', () => { }); it('should emit a change event', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('change', spy); await setValue('123'); @@ -183,7 +183,7 @@ describe('vwc-dial-pad', () => { }); it('should emit an input event', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input', spy); await setValue('123'); @@ -193,7 +193,7 @@ describe('vwc-dial-pad', () => { }); it('should prevent blur event after deleting the last value', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('blur', spy); await setValue('1'); @@ -213,7 +213,7 @@ describe('vwc-dial-pad', () => { describe('dial', function () { it('should fire dial event when clicked on call button', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); await elementUpdated(element); getCallButton().click(); @@ -221,7 +221,7 @@ describe('vwc-dial-pad', () => { }); it('should not fire dial event when enter is pressed on text field and pending', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); element.pending = true; await elementUpdated(element); @@ -232,7 +232,7 @@ describe('vwc-dial-pad', () => { }); it('should not fire dial event when enter is pressed on text field and disabled', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); element.disabled = true; await elementUpdated(element); @@ -243,7 +243,7 @@ describe('vwc-dial-pad', () => { }); it('should not fire dial event when enter is pressed on text field and callActive', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); element.callActive = true; await elementUpdated(element); @@ -254,7 +254,7 @@ describe('vwc-dial-pad', () => { }); it('should not fire dial event when enter is pressed on text field and noCall', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); element.noCall = true; await elementUpdated(element); @@ -265,7 +265,7 @@ describe('vwc-dial-pad', () => { }); it('should not fire dial event when enter is pressed on text field and value is empty', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); await elementUpdated(element); getTextField().dispatchEvent( @@ -275,7 +275,7 @@ describe('vwc-dial-pad', () => { }); it('should fire dial event when enter is pressed on input', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); await setValue('123'); const input: HTMLInputElement = getTextField().querySelector( @@ -292,7 +292,7 @@ describe('vwc-dial-pad', () => { }); it('should fire dial event with value when clicked on call button', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('dial', spy); await setValue('123'); getCallButton().click(); @@ -300,7 +300,7 @@ describe('vwc-dial-pad', () => { }); it('should prevent dial event when enter is pressed on delete button', async function () { - const spy = jest.fn(); + const spy = vi.fn(); await setValue('123'); element.addEventListener('dial', spy); getDeleteButton().dispatchEvent( @@ -310,7 +310,7 @@ describe('vwc-dial-pad', () => { }); it('should fire end-call event when clicked on call button when active', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('end-call', spy); element.callActive = true; await elementUpdated(element); @@ -328,7 +328,7 @@ describe('vwc-dial-pad', () => { function shouldFireEventOnceFromTextField(eventName: string) { it('should fire only once on the dial pad element', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); element.value = '123'; @@ -341,7 +341,7 @@ describe('vwc-dial-pad', () => { function shouldFireOnDialPadButtonClick(eventName: string) { it('should fire when user clicks the dial pad buttons', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); getDigitButtons().forEach((button) => { button.click(); @@ -354,7 +354,7 @@ describe('vwc-dial-pad', () => { function shouldSetElementValueAfterEvent(eventName: string) { it('should set element value after event', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); element.value = '123'; @@ -368,7 +368,7 @@ describe('vwc-dial-pad', () => { describe('keypad-click', function () { it('should fire keypad-click event when a keypad button is clicked', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('keypad-click', spy); await elementUpdated(element); getDigitButtons().forEach((button) => { @@ -378,7 +378,7 @@ describe('vwc-dial-pad', () => { }); it('should fire keypad-click event with the button which was clicked', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('keypad-click', spy); await elementUpdated(element); getDigitButtons().forEach((button) => { @@ -399,7 +399,7 @@ describe('vwc-dial-pad', () => { }); it('should prevent focus and blur events on subsequent keypad buttons', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('focus', spy); element.addEventListener('blur', spy); getDigitButtons().forEach((button) => { @@ -414,7 +414,7 @@ describe('vwc-dial-pad', () => { describe('focus event', () => { const eventName = 'focus'; it('should prevent propagation of focus event from textfield', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); element.value = '123'; @@ -428,7 +428,7 @@ describe('vwc-dial-pad', () => { describe('blur event', () => { const eventName = 'blur'; it('should prevent propagation of blur event from textfield', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener(eventName, spy); element.value = '123'; diff --git a/libs/components/src/lib/dialog/dialog.spec.ts b/libs/components/src/lib/dialog/dialog.spec.ts index 73fb71b8de..78a0938b86 100644 --- a/libs/components/src/lib/dialog/dialog.spec.ts +++ b/libs/components/src/lib/dialog/dialog.spec.ts @@ -199,7 +199,7 @@ describe('vwc-dialog', () => { it('should fire the "close" event only when closing', async function () { await closeDialog(); - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); await closeDialog(); @@ -298,7 +298,7 @@ describe('vwc-dialog', () => { const returnValue = 'returnValue'; element.returnValue = returnValue; await showDialog(); - const spy = jest.fn().mockImplementation((e) => (detail = e.detail)); + const spy = vi.fn().mockImplementation((e) => (detail = e.detail)); element.addEventListener('close', spy); await closeDialog(); @@ -308,7 +308,7 @@ describe('vwc-dialog', () => { it("should not bubble 'close' event", async () => { await showDialog(); - const fn = jest.fn(); + const fn = vi.fn(); element.parentElement!.addEventListener('close', fn); await closeDialog(); @@ -319,7 +319,7 @@ describe('vwc-dialog', () => { describe('open event', function () { it("should fire 'open' event when opened", async function () { - const onOpen = jest.fn(); + const onOpen = vi.fn(); element.addEventListener('open', onOpen); await showDialog(); @@ -328,7 +328,7 @@ describe('vwc-dialog', () => { }); it('should not bubble', async () => { - const onOpen = jest.fn(); + const onOpen = vi.fn(); element.parentElement!.addEventListener('open', onOpen); await showDialog(); @@ -355,7 +355,7 @@ describe('vwc-dialog', () => { }); it('should emit a non-bubbling event', async () => { - const onCancel = jest.fn(); + const onCancel = vi.fn(); element.parentElement!.addEventListener('cancel', onCancel); triggerCancelEvent(); @@ -410,9 +410,9 @@ describe('vwc-dialog', () => { beforeEach(async function () { element.headline = 'headline'; await showModalDialog(); - jest - .spyOn(dialogEl, 'getBoundingClientRect') - .mockImplementation(() => dialogClientRect); + vi.spyOn(dialogEl, 'getBoundingClientRect').mockImplementation( + () => dialogClientRect + ); }); it('should leave the dialog open when mouseup or click', async function () { @@ -447,7 +447,7 @@ describe('vwc-dialog', () => { }); it('should emit a cancel event when scrim is clicked', async function () { - const cancelSpy = jest.fn(); + const cancelSpy = vi.fn(); element.addEventListener('cancel', cancelSpy); clickOnScrim(); await elementUpdated(element); @@ -531,7 +531,7 @@ describe('vwc-dialog', () => { }); it('should close the dialog when dismiss button is clicked', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); await showDialog(); @@ -542,7 +542,7 @@ describe('vwc-dialog', () => { }); it('should emit a cancel event when dismiss button is clicked', async function () { - const cancelSpy = jest.fn(); + const cancelSpy = vi.fn(); element.addEventListener('cancel', cancelSpy); await showDialog(); @@ -554,7 +554,7 @@ describe('vwc-dialog', () => { it('should preventDefault of cancel events on the dialog', async () => { const cancelEvent = new Event('cancel'); - cancelEvent.preventDefault = jest.fn(); + cancelEvent.preventDefault = vi.fn(); await showDialog(); dialogEl.dispatchEvent(cancelEvent); @@ -660,7 +660,7 @@ describe('vwc-dialog', () => { }); it('should fire cancel event on escape key press', async function () { - const cancelSpy = jest.fn(); + const cancelSpy = vi.fn(); element.addEventListener('cancel', cancelSpy); await showModalDialog(); await triggerEscapeKey(); @@ -675,7 +675,7 @@ describe('vwc-dialog', () => { it('should stop propgation on escape key', async () => { await showModalDialog(); - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); getBaseElement(element).dispatchEvent( new KeyboardEvent('keydown', { key: 'Escape' }) @@ -687,7 +687,7 @@ describe('vwc-dialog', () => { it('should preventDefaut if Escape was pressed', async () => { await showModalDialog(); const event = new KeyboardEvent('keydown', { key: 'Escape' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(1); @@ -696,7 +696,7 @@ describe('vwc-dialog', () => { it('should enable default if key is not Escape', async () => { await showModalDialog(); const event = new KeyboardEvent('keydown', { key: ' ' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); diff --git a/libs/components/src/lib/file-picker/file-picker.spec.ts b/libs/components/src/lib/file-picker/file-picker.spec.ts index ad5a32862f..5b71afd092 100644 --- a/libs/components/src/lib/file-picker/file-picker.spec.ts +++ b/libs/components/src/lib/file-picker/file-picker.spec.ts @@ -324,6 +324,7 @@ describe('vwc-file-picker', () => { expect(element.files).toEqual([file2]); }); }); + describe('accept', function () { it('should show an error message for files added that do not match the accept attribute', async function () { element.accept = 'image/*, text/html, .zip'; @@ -341,6 +342,24 @@ describe('vwc-file-picker', () => { expect(getErrorMessage(3)).toBe("You can't select files of this type."); }); + it('should display message from error property if error message is an object', async () => { + element.accept = 'image/*, text/html, .zip'; + (element.invalidFileTypeError as any) = { + error: 'error from object', + }; + + await elementUpdated(element); + + addFiles([ + await generateFile('london.png', 1, 'image/png'), + await generateFile('london.html', 1, 'text/html'), + await generateFile('london.zip', 1, 'application/zip'), + await generateFile('london.txt', 1, 'text/plain'), + ]); + + expect(getErrorMessage(3)).toBe('error from object'); + }); + it('should show an custom error message (when supplied) for files added that do not match the accept attribute', async function () { element.accept = 'image/*, text/html, .zip'; element.invalidFileTypeError = 'File type not allowed.'; @@ -404,7 +423,7 @@ describe('vwc-file-picker', () => { describe('change', function () { it('should fire "change" event after a file is added', async () => { let filesLengthInChangeHandler = -1; - const onChange = jest.fn().mockImplementation(() => { + const onChange = vi.fn().mockImplementation(() => { filesLengthInChangeHandler = element.files.length; }); element.addEventListener('change', onChange); @@ -418,7 +437,7 @@ describe('vwc-file-picker', () => { it('should fire "change" event after a file is removed', async () => { addFiles([await generateFile('london.png', 1)]); let filesLengthInChangeHandler = -1; - const onChange = jest.fn().mockImplementation(() => { + const onChange = vi.fn().mockImplementation(() => { filesLengthInChangeHandler = element.files.length; }); element.addEventListener('change', onChange); @@ -472,7 +491,7 @@ describe('vwc-file-picker', () => { ])( 'should click on the hidden file input when pressing %s key', async function (_, key) { - const hiddenInputClick = jest.fn(); + const hiddenInputClick = vi.fn(); const hiddenInput = getHiddenInput(); hiddenInput.click = hiddenInputClick; diff --git a/libs/components/src/lib/file-picker/file-picker.ts b/libs/components/src/lib/file-picker/file-picker.ts index 49424fbc7d..0ae22f4a3e 100644 --- a/libs/components/src/lib/file-picker/file-picker.ts +++ b/libs/components/src/lib/file-picker/file-picker.ts @@ -224,7 +224,7 @@ export class FilePicker extends FormAssociatedFilePicker { #localizeErrorMessage = (file: DropzoneFile, message: string | any) => { if (file.previewElement) { file.previewElement.classList.add('dz-error'); - // istanbul ignore next + if (typeof message !== 'string' && message.error) { message = message.error; } diff --git a/libs/components/src/lib/icon/__snapshots__/icon.spec.ts.snap b/libs/components/src/lib/icon/__snapshots__/icon.spec.ts.snap index 40cce5113f..ceecf63c24 100644 --- a/libs/components/src/lib/icon/__snapshots__/icon.spec.ts.snap +++ b/libs/components/src/lib/icon/__snapshots__/icon.spec.ts.snap @@ -1,14 +1,14 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`icon name should set the icon as loading after 500ms 1`] = ` -" +exports[`icon > name > should set the icon as loading after 500ms 1`] = ` +" - - + + - + - + diff --git a/libs/components/src/lib/icon/icon.spec.ts b/libs/components/src/lib/icon/icon.spec.ts index 407449cc70..8a214b1075 100644 --- a/libs/components/src/lib/icon/icon.spec.ts +++ b/libs/components/src/lib/icon/icon.spec.ts @@ -1,4 +1,9 @@ -import { axe, fixture, getControlElement } from '@vivid-nx/shared'; +import { + axe, + elementUpdated, + fixture, + getControlElement, +} from '@vivid-nx/shared'; import { ICONS_VERSION as ICON_SET_VERSION } from '@vonage/vwc-consts'; import type { Icon } from './icon'; import '.'; @@ -7,55 +12,47 @@ const COMPONENT_TAG = 'vwc-icon'; describe('icon', function () { function fakeFetch(requestTime = 4000) { - (global.fetch as any) = jest.fn((_, { signal }) => { + (global.fetch as any) = vi.fn((_, { signal }) => { currentFetchSignal = signal; return new Promise((res) => { - setTimeout(() => res(response), requestTime); + setTimeout(() => { + res(response); + }, requestTime); }); }); } - function setIconNameAndTriggerFirstTimer() { - element.name = 'none'; - jest.advanceTimersToNextTimer(); - } - - function setIconNameAndAdvanceTime(timeInMs: number, name = 'none') { - element.name = name; - jest.advanceTimersByTime(timeInMs); - } - - function setIconNameAndRunAllTimers(iconName: string | undefined) { - element.name = iconName; - jest.runAllTimers(); - } + // Use a unique id for each icon name to avoid caching + let nextUniqueId = 0; + const uniqueId = () => `icon-${nextUniqueId++}`; const svg = 'svg'; - const response = { - ok: true, - headers: { - get: () => { - return 'image/svg+xml'; - }, - }, - text: () => svg, - }; + let response: any; + let responseFileType = 'image/svg+xml'; + const originalFetch = global.fetch; - const originalPromise = global.Promise; let currentFetchSignal: AbortSignal; let element: Icon; beforeEach(async () => { element = (await fixture(`<${COMPONENT_TAG}>`)) as Icon; - global.Promise = require('promise'); // needed in order for promises to work with jest fake timers - jest.useFakeTimers({ legacyFakeTimers: true }); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + responseFileType = 'image/svg+xml'; + response = { + ok: true, + headers: { + get: () => { + return responseFileType; + }, + }, + text: () => svg, + }; }); afterEach(function () { - jest.useRealTimers(); + vi.useRealTimers(); global.fetch = originalFetch; - global.Promise = originalPromise; }); it('should allow being created via createElement', () => { @@ -66,44 +63,77 @@ describe('icon', function () { }); describe('name', function () { + it('should leave svg undefined if fetch fails', async function () { + response.ok = false; + fakeFetch(100); + + // Set the name property to trigger the fetch call + element.name = uniqueId(); + + // Advance the timers to ensure the fetch call is made + await vi.advanceTimersByTimeAsync(100); + + expect(element._svg).toEqual(undefined); + }); + + it('should leave svg undefined if fetch returns non svg content-type', async function () { + responseFileType = 'text/plain'; + fakeFetch(100); + + // Set the name property to trigger the fetch call + element.name = uniqueId(); + + // Advance the timers to ensure the fetch call is made + await vi.advanceTimersByTimeAsync(100); + + expect(element._svg).toEqual(undefined); + }); + it('should show nothing when first changing the icon', async function () { fakeFetch(4000); - setIconNameAndTriggerFirstTimer(); + element.name = uniqueId(); expect(element._svg).toEqual(undefined); }); it('should set the icon as loading after 500ms', async function () { fakeFetch(4000); - setIconNameAndAdvanceTime(500); + element.name = uniqueId(); + await vi.advanceTimersByTimeAsync(500); expect(element._svg).toMatchSnapshot(); }); it('should remove loading icon after 2500ms', async function () { fakeFetch(4000); - setIconNameAndAdvanceTime(2500); + element.name = uniqueId(); + await vi.advanceTimersByTimeAsync(2500); expect(element._svg).toEqual(undefined); }); it('should set icon in svg after icon fetch', async function () { fakeFetch(100); - setIconNameAndRunAllTimers('none'); + element.name = uniqueId(); + await vi.runAllTimersAsync(); expect(element._svg).toEqual(svg); }); - it('should show empty string when no icon is available', function () { + it('should show empty string when no icon is available', async function () { fakeFetch(100); - setIconNameAndRunAllTimers('none'); - setIconNameAndRunAllTimers(undefined); + element.name = uniqueId(); + await vi.runAllTimersAsync(); + element.name = undefined; + await vi.runAllTimersAsync(); expect(element._svg).toEqual(''); }); - it('should abort additional fetch requests', function () { + it('should abort additional fetch requests', async function () { fakeFetch(100); - setIconNameAndAdvanceTime(50, 'home'); + element.name = uniqueId(); + await vi.advanceTimersByTimeAsync(50); const homeSignal = currentFetchSignal; - setIconNameAndAdvanceTime(10, 'user'); + element.name = 'user'; + await vi.advanceTimersByTimeAsync(10); expect(homeSignal.aborted).toBe(true); }); @@ -115,24 +145,29 @@ describe('icon', function () { }); it('should set to true when icon is loaded', async function () { - setIconNameAndRunAllTimers('home'); + element.name = uniqueId(); + await vi.runAllTimersAsync(); expect(element.iconLoaded).toEqual(true); }); it('should set an image with src when iconLoaded is false', async function () { - setIconNameAndRunAllTimers('home'); + const iconName = uniqueId(); + element.name = iconName; + await vi.runAllTimersAsync(); element.iconLoaded = false; - jest.runAllTimers(); + await elementUpdated(element); const imgElement = getControlElement(element).querySelector('img'); expect(imgElement?.src).toEqual( - `https://icon.resources.vonage.com/v${ICON_SET_VERSION}/home.svg` + `https://icon.resources.vonage.com/v${ICON_SET_VERSION}/${iconName}.svg` ); }); it('should set aria-busy on the figure element when iconLoaded is false', async function () { - setIconNameAndRunAllTimers('home'); + element.name = uniqueId(); + await vi.runAllTimersAsync(); element.iconLoaded = false; - jest.runAllTimers(); + + await elementUpdated(element); const figureElement = getControlElement(element); expect(figureElement.hasAttribute('aria-busy')).toBe(true); }); @@ -140,7 +175,8 @@ describe('icon', function () { it('should set iconLoaded to false when name changes', async function () { element.iconLoaded = true; - setIconNameAndAdvanceTime(100); + element.name = uniqueId(); + await vi.advanceTimersByTimeAsync(100); expect(element.iconLoaded).toEqual(false); }); @@ -155,7 +191,7 @@ describe('icon', function () { 'should set size class accordingly when size is %s', async function (size) { element.size = size; - jest.runAllTimers(); + await elementUpdated(element); expect(getControlElement(element).classList).toContain(`size-${size}`); } ); @@ -163,8 +199,8 @@ describe('icon', function () { describe('a11y', () => { it('should pass html a11y test', async () => { - jest.clearAllTimers(); - jest.useRealTimers(); + vi.clearAllTimers(); + vi.useRealTimers(); element = (await fixture( `<${COMPONENT_TAG} name="home">` )) as Icon; diff --git a/libs/components/src/lib/menu-item/menu-item.spec.ts b/libs/components/src/lib/menu-item/menu-item.spec.ts index 86d2a8d559..cc030e8219 100644 --- a/libs/components/src/lib/menu-item/menu-item.spec.ts +++ b/libs/components/src/lib/menu-item/menu-item.spec.ts @@ -129,7 +129,7 @@ describe('vwc-menu-item', () => { ); it('should enable default of click event if role is presentation', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('click', spy); (element as any).role = MenuItemRole.presentation; await elementUpdated(element); @@ -139,7 +139,7 @@ describe('vwc-menu-item', () => { }); it('should prevent default of click event if role is not presentation', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('click', spy); (element as any).role = MenuItemRole.menuitem; await elementUpdated(element); @@ -334,9 +334,9 @@ describe('vwc-menu-item', () => { }); describe('change event', () => { - let changeSpy: jest.Mock; + let changeSpy: vi.Mock; beforeEach(async () => { - changeSpy = jest.fn(); + changeSpy = vi.fn(); element.addEventListener('change', changeSpy); }); @@ -374,9 +374,9 @@ describe('vwc-menu-item', () => { }); describe('click event', () => { - let clickSpy: jest.Mock; + let clickSpy: vi.Mock; beforeEach(async () => { - clickSpy = jest.fn(); + clickSpy = vi.fn(); element.addEventListener('click', clickSpy); }); @@ -431,7 +431,7 @@ describe('vwc-menu-item', () => { describe('expanded-change event', () => { it('should fire "expanded-change" event when submenu exists and expanded changes', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('expanded-change', spy); addSubmenu(); await elementUpdated(element); diff --git a/libs/components/src/lib/menu/menu.spec.ts b/libs/components/src/lib/menu/menu.spec.ts index c6496da1c4..fa14e664fa 100644 --- a/libs/components/src/lib/menu/menu.spec.ts +++ b/libs/components/src/lib/menu/menu.spec.ts @@ -229,7 +229,7 @@ describe('vwc-menu', () => { it('should allow propgation on escape key and menu is closed', async () => { element.open = false; - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); @@ -238,7 +238,7 @@ describe('vwc-menu', () => { it('should stop propgation on escape key and menu is open', async () => { element.open = true; - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); @@ -246,14 +246,14 @@ describe('vwc-menu', () => { }); it('should allow default if Escape was pressed', async () => { - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); }); it('should enable default if key is not Escape', async () => { - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); @@ -361,7 +361,7 @@ describe('vwc-menu', () => { }); it('should not prevent default of other keydown events', () => { - const keydownSpy = jest.fn(); + const keydownSpy = vi.fn(); element.addEventListener('keydown', keydownSpy); item1.focus(); @@ -632,7 +632,7 @@ describe('vwc-menu', () => { describe('open event', () => { it('should dispatch a non-bubbling open event when the menu is opened', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('open', spy); element.open = true; @@ -646,7 +646,7 @@ describe('vwc-menu', () => { describe('close event', () => { it('should dispatch a non-bubbling close event when the menu is close', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); element.open = true; await elementUpdated(element); diff --git a/libs/components/src/lib/nav-disclosure/nav-disclosure.spec.ts b/libs/components/src/lib/nav-disclosure/nav-disclosure.spec.ts index 7581161f0e..08b0f6acfb 100644 --- a/libs/components/src/lib/nav-disclosure/nav-disclosure.spec.ts +++ b/libs/components/src/lib/nav-disclosure/nav-disclosure.spec.ts @@ -82,7 +82,7 @@ describe('vwc-nav-disclosure', () => { describe('toggle event', () => { it('should emit a toggle event that does not bubble when open/closed state is toggled', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('toggle', spy); element.details.dispatchEvent(new Event('toggle')); diff --git a/libs/components/src/lib/number-field/number-field.spec.ts b/libs/components/src/lib/number-field/number-field.spec.ts index 53062181d1..e7a7b010e4 100644 --- a/libs/components/src/lib/number-field/number-field.spec.ts +++ b/libs/components/src/lib/number-field/number-field.spec.ts @@ -16,7 +16,7 @@ import deDE from '../../locales/de-DE'; import { NumberField } from './number-field'; import '.'; -const COMPONENT_TAG_NAME = 'vwc-number-field'; +const COMPONENT_TAG = 'vwc-number-field'; describe('vwc-number-field', () => { function setToBlurred() { @@ -33,7 +33,7 @@ describe('vwc-number-field', () => { beforeEach(async () => { element = (await fixture( - `<${COMPONENT_TAG_NAME}>` + `<${COMPONENT_TAG}>` )) as NumberField; control = getControlElement(element) as HTMLInputElement; }); @@ -48,7 +48,7 @@ describe('vwc-number-field', () => { // createElement may fail even though indirect instantiation through innerHTML etc. succeeds // This is because only createElement performs checks for custom element constructor requirements // See https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance - expect(() => document.createElement(COMPONENT_TAG_NAME)).not.toThrow(); + expect(() => document.createElement(COMPONENT_TAG)).not.toThrow(); }); }); @@ -128,7 +128,7 @@ describe('vwc-number-field', () => { }); it('should focus itself when connected if set', async function () { - element.focus = jest.fn(); + element.focus = vi.fn(); element.autofocus = true; await elementUpdated(element); element.remove(); @@ -212,7 +212,7 @@ describe('vwc-number-field', () => { it('should attach to closest form', async function () { const { form: formElement } = createFormHTML({ - componentTagName: COMPONENT_TAG_NAME, + componentTagName: COMPONENT_TAG, fieldName, fieldValue, formId, @@ -234,7 +234,7 @@ describe('vwc-number-field', () => { fieldValue, formId, otherFormId: 'otherFormId', - componentTagName: COMPONENT_TAG_NAME, + componentTagName: COMPONENT_TAG, formWrapper, }); @@ -252,7 +252,7 @@ describe('vwc-number-field', () => { fieldName, fieldValue, formId, - componentTagName: COMPONENT_TAG_NAME, + componentTagName: COMPONENT_TAG, formWrapper, }); @@ -529,9 +529,9 @@ describe('vwc-number-field', () => { }); describe('select', function () { - const onSelect = jest.fn(); + const onSelect = vi.fn(); beforeEach(() => { - control.select = jest.fn(); + control.select = vi.fn(); element.addEventListener('select', onSelect); }); @@ -569,7 +569,7 @@ describe('vwc-number-field', () => { 'should prevent default of %s key presses', async () => { const event = new KeyboardEvent('keydown', { key: 'ArrowDown' }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); control.dispatchEvent(event); @@ -579,7 +579,7 @@ describe('vwc-number-field', () => { it('should not prevent default of other key presses', async () => { const event = new KeyboardEvent('keydown', { key: 'A' }); - event.preventDefault = jest.fn(); + event.preventDefault = vi.fn(); control.dispatchEvent(event); diff --git a/libs/components/src/lib/option/option.spec.ts b/libs/components/src/lib/option/option.spec.ts index 66a6ac41be..d4cd74507a 100644 --- a/libs/components/src/lib/option/option.spec.ts +++ b/libs/components/src/lib/option/option.spec.ts @@ -146,7 +146,7 @@ describe('vwc-option', () => { }); // Does not work: - xit('should return the parent form', async () => { + it.skip('should return the parent form', async () => { const form = document.createElement('form'); form.appendChild(element); document.body.appendChild(form); diff --git a/libs/components/src/lib/popup/popup.spec.ts b/libs/components/src/lib/popup/popup.spec.ts index cff9e86cb4..d6f6144de1 100644 --- a/libs/components/src/lib/popup/popup.spec.ts +++ b/libs/components/src/lib/popup/popup.spec.ts @@ -12,6 +12,13 @@ import '.'; const COMPONENT_TAG = 'vwc-popup'; +vi.mock('@floating-ui/dom', async (getModule) => { + return { + // Allow spying on exported functions + ...(await getModule()), + }; +}); + describe('vwc-popup', () => { let element: Popup; let anchor: Button; @@ -36,7 +43,11 @@ describe('vwc-popup', () => { }); afterEach(function () { - jest.clearAllMocks(); + // Ensure floating-ui is cleaned up and will make no more callbacks + element.remove(); + vi.clearAllTimers(); + + vi.restoreAllMocks(); }); it('should allow being created via createElement', () => { @@ -47,25 +58,25 @@ describe('vwc-popup', () => { }); describe('cleanup autoUpdate', () => { + let cleanupMock: vi.Mock; + beforeEach(() => { + cleanupMock = vi.fn(); + vi.spyOn(floatingUI, 'autoUpdate').mockReturnValue(cleanupMock); + }); + it('should cleanup autoUpdate when element is removed', async function () { - const cleanupMock = jest.fn(); - jest.spyOn(floatingUI, 'autoUpdate').mockReturnValue(cleanupMock); await setupPopupToOpenWithAnchor(); element.remove(); expect(cleanupMock).toHaveBeenCalled(); }); it('should cleanup autoUpdate when popup is closed', async function () { - const cleanupMock = jest.fn(); - jest.spyOn(floatingUI, 'autoUpdate').mockReturnValue(cleanupMock); await setupPopupToOpenWithAnchor(); element.open = false; expect(cleanupMock).toHaveBeenCalled(); }); it('should cleanup autoUpdate when anchor is removed', async function () { - const cleanupMock = jest.fn(); - jest.spyOn(floatingUI, 'autoUpdate').mockReturnValue(cleanupMock); await setupPopupToOpenWithAnchor(); element.anchor = undefined; expect(cleanupMock).toHaveBeenCalled(); @@ -102,16 +113,12 @@ describe('vwc-popup', () => { }; beforeEach(function () { - jest.spyOn(floatingUI, 'computePosition'); - }); - - afterEach(function () { - (floatingUI.computePosition as jest.MockedFunction).mockRestore(); + vi.spyOn(floatingUI, 'computePosition'); }); function resetPosition(hidden = true) { computePositionResult.middlewareData.hide.referenceHidden = hidden; - (floatingUI.computePosition as jest.MockedFunction).mockReturnValue( + (floatingUI.computePosition as vi.MockedFunction).mockReturnValue( Promise.resolve(computePositionResult) ); } @@ -134,6 +141,8 @@ describe('vwc-popup', () => { it('should set the arrow in a position according to middleware', async function () { element.arrow = true; + // FIXME: when removing the elementUpdate, breaks because arrowEl not available + await elementUpdated(element); await setupPopupToOpenWithAnchor(); (computePositionResult.middlewareData.arrow as any) = { x: 5, y: 10 }; await resetPosition(false); @@ -170,13 +179,8 @@ describe('vwc-popup', () => { describe('open', () => { beforeEach(() => { - jest.spyOn(floatingUI, 'autoUpdate'); - jest.spyOn(floatingUI, 'computePosition'); - }); - - afterEach(() => { - jest.mocked(floatingUI.autoUpdate).mockRestore(); - jest.mocked(floatingUI.computePosition).mockRestore(); + vi.spyOn(floatingUI, 'autoUpdate'); + vi.spyOn(floatingUI, 'computePosition'); }); it('should hide control if not set', async () => { @@ -192,13 +196,13 @@ describe('vwc-popup', () => { it('should ensure that the popup is visible when calling computePosition', async function () { let popupVisibleWhenComputePositionIsCalled = false; - jest - .mocked(floatingUI.computePosition) - .mockImplementationOnce((...args: [any, any, any]) => { + vi.mocked(floatingUI.computePosition).mockImplementationOnce( + (...args: [any, any, any]) => { popupVisibleWhenComputePositionIsCalled = getControlElement(element).classList.contains('open'); return floatingUI.computePosition(...args); - }); + } + ); await setupPopupToOpenWithAnchor(); @@ -217,7 +221,7 @@ describe('vwc-popup', () => { const popoverEl = element.shadowRoot!.querySelector( '[popover]' ) as HTMLElement; - popoverEl.showPopover = jest.fn(); + popoverEl.showPopover = vi.fn(); element.show(); @@ -226,7 +230,7 @@ describe('vwc-popup', () => { }); it('should fire vwc-popup:open event', async function () { - const spyOpen = jest.fn(); + const spyOpen = vi.fn(); element.addEventListener('vwc-popup:open', spyOpen); element.show(); @@ -246,7 +250,7 @@ describe('vwc-popup', () => { it('should fire "vwc-popup:close" event', async function () { element.open = true; - const spyClose = jest.fn(); + const spyClose = vi.fn(); element.addEventListener('vwc-popup:close', spyClose); element.hide(); @@ -314,11 +318,7 @@ describe('vwc-popup', () => { describe('placementStrategy', () => { beforeEach(() => { - jest.spyOn(floatingUI, 'computePosition'); - }); - - afterEach(() => { - jest.mocked(floatingUI.computePosition).mockRestore(); + vi.spyOn(floatingUI, 'computePosition'); }); it('should use placementStrategy to compute position', async () => { @@ -343,7 +343,7 @@ describe('vwc-popup', () => { describe('animationFrame', () => { function resetMethodCallCount(property: any) { - jest.spyOn(element, property).mockReset(); + vi.spyOn(element, property).mockReset(); } async function openPopup() { @@ -370,21 +370,18 @@ describe('vwc-popup', () => { bottom: 1, left: 1, } as DOMRect; - jest - .spyOn(HTMLElement.prototype, 'getBoundingClientRect') - .mockReturnValue({ ...clientRect, ...overrides }); + vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + ...clientRect, + ...overrides, + }); } - let rAFStub: any; + let rAFStub: vi.MockInstance; beforeEach(async () => { element.anchor = anchor; await elementUpdated(element); - rAFStub = jest.spyOn(window, 'requestAnimationFrame'); - }); - - afterEach(() => { - jest.mocked(window.requestAnimationFrame).mockRestore(); + rAFStub = vi.spyOn(window, 'requestAnimationFrame'); }); it('should disable recursive calls to requestAnimationFrame when false', async () => { diff --git a/libs/components/src/lib/radio-group/radio-group.spec.ts b/libs/components/src/lib/radio-group/radio-group.spec.ts index 2d00aa01d4..cca6afcdc3 100644 --- a/libs/components/src/lib/radio-group/radio-group.spec.ts +++ b/libs/components/src/lib/radio-group/radio-group.spec.ts @@ -197,7 +197,7 @@ describe('vwc-radio-group', () => { describe('change', () => { it('should be fired when a user toggles the radio-group', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('change', spy); getBaseElement(radios[2]).click(); diff --git a/libs/components/src/lib/radio/radio.internals.spec.ts b/libs/components/src/lib/radio/radio.internals.spec.ts new file mode 100644 index 0000000000..40dea4b4ab --- /dev/null +++ b/libs/components/src/lib/radio/radio.internals.spec.ts @@ -0,0 +1,166 @@ +import { elementUpdated, fixture } from '@vivid-nx/shared'; +import { Radio } from './radio'; +import '.'; + +const COMPONENT_TAG = 'vwc-radio'; + +describe('vwc-radio', () => { + let element: Radio; + + beforeEach(async () => { + element = fixture(`<${COMPONENT_TAG}>`) as Radio; + await elementUpdated(element); + }); + + describe('require with internals', () => { + function mockElementInternals() { + function addElementInternals(this: Radio) { + let internals = InternalsMap.get(this); + + if (!internals) { + internals = (this as any).attachInternals(); + InternalsMap.set(this, internals); + } + + return internals; + } + + internalsMock = vi + .spyOn(Radio.prototype, 'elementInternals', 'get') + .mockImplementation(addElementInternals); + } + + function mockFormAssociated() { + formAssociatedMock = vi + .spyOn(Radio as any, 'formAssociated', 'get') + .mockReturnValue(true); + } + + const InternalsMap = new WeakMap(); + const wrapper = document.createElement('div'); + let elementWithInternals: Radio; + let sibling: Radio; + let internalsMock: vi.SpyInstance | null = null; + let formAssociatedMock: vi.SpyInstance | null = null; + + beforeAll(async () => { + await import('element-internals-polyfill'); + document.body.appendChild(wrapper); + }); + + afterAll(() => { + wrapper.remove(); + internalsMock?.mockRestore(); + formAssociatedMock?.mockRestore(); + internalsMock = null; + formAssociatedMock = null; + (window as any).ElementInternals = undefined; + (HTMLElement.prototype as any).attachInternals = undefined; + }); + + beforeEach(async () => { + mockFormAssociated(); + mockElementInternals(); + elementWithInternals = document.createElement(COMPONENT_TAG) as Radio; + sibling = document.createElement(COMPONENT_TAG) as Radio; + wrapper.appendChild(elementWithInternals); + wrapper.appendChild(sibling); + await elementUpdated(elementWithInternals); + }); + + afterEach(() => { + wrapper.innerHTML = ''; + }); + + it('should sync validity from siblings in same group and name when all unchecked', async () => { + sibling = document.createElement(COMPONENT_TAG) as Radio; + sibling.name = elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = true; + + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(true); + }); + + it('should sync validity with siblings in same group and name when checked', async () => { + sibling.name = elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = true; + sibling.checked = true; + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(false); + }); + + it('should set the correct valueMissing validity when added to the DOM with valid group', async () => { + sibling.name = elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = true; + elementWithInternals.checked = true; + + await elementUpdated(elementWithInternals); + + expect(sibling.validity.valueMissing).toBe(false); + }); + + it("should sync siblings' validity.valueMissing when added as checked", async () => { + sibling.name = elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = true; + sibling.checked = true; + + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(false); + }); + + it('should sync values when name changes to that of siblings', async () => { + sibling.name = 'not-test'; + elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = true; + sibling.checked = true; + + await elementUpdated(elementWithInternals); + + const valueWhenNameIsDifferent = + elementWithInternals.validity.valueMissing; + + elementWithInternals.name = sibling.name; + await elementUpdated(elementWithInternals); + + expect(valueWhenNameIsDifferent).toBe(true); + expect(elementWithInternals.validity.valueMissing).toBe(false); + }); + + it('should validate valueMissing on a single radio only when required is set', async () => { + elementWithInternals.name = 'test'; + elementWithInternals.required = false; + + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(false); + }); + + it('should validate valueMissing on radio-group only when required is set', async () => { + sibling.name = 'test'; + elementWithInternals.name = 'test'; + sibling.required = elementWithInternals.required = false; + + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(false); + expect(sibling.validity.valueMissing).toBe(false); + }); + + it('should validate valueMissing when required is set on at least one sibling', async () => { + sibling.name = elementWithInternals.name = 'test'; + + sibling.required = false; + elementWithInternals.required = true; + await elementUpdated(elementWithInternals); + + sibling.checked = true; + await elementUpdated(elementWithInternals); + + expect(elementWithInternals.validity.valueMissing).toBe(false); + expect(sibling.validity.valueMissing).toBe(false); + }); + }); +}); diff --git a/libs/components/src/lib/radio/radio.spec.ts b/libs/components/src/lib/radio/radio.spec.ts index 7e99e2e5b1..d93dafbbf1 100644 --- a/libs/components/src/lib/radio/radio.spec.ts +++ b/libs/components/src/lib/radio/radio.spec.ts @@ -23,18 +23,10 @@ async function setBoolAttributeOn( describe('vwc-radio', () => { let element: Radio; - let internalsMock: jest.SpyInstance | null = null; - let formAssociatedMock: jest.SpyInstance | null = null; beforeEach(async () => { element = fixture(`<${COMPONENT_TAG}>`) as Radio; - }); - - afterEach(async () => { - internalsMock?.mockRestore(); - formAssociatedMock?.mockRestore(); - internalsMock = null; - formAssociatedMock = null; + await elementUpdated(element); }); describe('basic', () => { @@ -114,7 +106,7 @@ describe('vwc-radio', () => { it('should not prevent default of keypress other than Space', () => { const event = new KeyboardEvent('keypress', { key: 'Enter' }); - const spy = jest.spyOn(event, 'preventDefault'); + const spy = vi.spyOn(event, 'preventDefault'); getBaseElement(element).dispatchEvent(event); expect(spy).not.toHaveBeenCalled(); }); @@ -188,159 +180,11 @@ describe('vwc-radio', () => { expect(element.proxy.getAttribute('name')).toBeNull(); }); - - describe('with internals', () => { - function mockElementInternals() { - function addElementInternals(this: Radio) { - let internals = InternalsMap.get(this); - - if (!internals) { - internals = (this as any).attachInternals(); - InternalsMap.set(this, internals); - } - - return internals; - } - - return (internalsMock = jest - .spyOn(Radio.prototype, 'elementInternals', 'get') - .mockImplementation(addElementInternals)); - } - - function mockFormAssociated() { - return (formAssociatedMock = jest - .spyOn(Radio as any, 'formAssociated', 'get') - .mockReturnValue(true)); - } - - const InternalsMap = new WeakMap(); - - beforeEach(async () => { - await import('element-internals-polyfill'); - mockFormAssociated(); - mockElementInternals(); - }); - - it('should sync validity from siblings in same group and name when all unchecked', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = element.name = 'test'; - sibling.required = element.required = true; - - element.parentElement?.appendChild(sibling); - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(true); - }); - - it('should sync validity with siblings in same group and name when checked', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = element.name = 'test'; - sibling.required = element.required = true; - - element.parentElement?.appendChild(sibling); - await elementUpdated(element); - sibling.checked = true; - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(false); - }); - - it('should set the correct valueMissing validity when added to the DOM with valid group', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = element.name = 'test'; - sibling.required = element.required = true; - element.checked = true; - - await elementUpdated(element); - element.parentElement?.appendChild(sibling); - - await elementUpdated(element); - - expect(sibling.validity.valueMissing).toBe(false); - }); - - it("should sync siblings' validity.valueMissing when added as checked", async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = element.name = 'test'; - sibling.required = element.required = true; - sibling.checked = true; - - await elementUpdated(element); - element.parentElement?.appendChild(sibling); - - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(false); - }); - - it('should sync values when name changes to that of siblings', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = 'not-test'; - element.name = 'test'; - sibling.required = element.required = true; - sibling.checked = true; - - await elementUpdated(element); - element.parentElement?.appendChild(sibling); - - await elementUpdated(element); - - const valueWhenNameIsDifferent = element.validity.valueMissing; - - element.name = sibling.name; - await elementUpdated(element); - - expect(valueWhenNameIsDifferent).toBe(true); - expect(element.validity.valueMissing).toBe(false); - }); - - it('should validate valueMissing on a single radio only when required is set', async () => { - element.name = 'test'; - element.required = false; - - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(false); - }); - - it('should validate valueMissing on radio-group only when required is set', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = 'test'; - element.name = 'test'; - sibling.required = element.required = false; - - await elementUpdated(element); - element.parentElement?.appendChild(sibling); - - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(false); - expect(sibling.validity.valueMissing).toBe(false); - }); - - it('should validate valueMissing when required is set on at least one sibling', async () => { - const sibling = document.createElement(COMPONENT_TAG) as Radio; - sibling.name = element.name = 'test'; - - sibling.required = false; - element.required = true; - await elementUpdated(element); - - element.parentElement?.appendChild(sibling); - await elementUpdated(element); - - sibling.checked = true; - await elementUpdated(element); - - expect(element.validity.valueMissing).toBe(false); - expect(sibling.validity.valueMissing).toBe(false); - }); - }); }); describe('change', () => { it('should be fired when a user toggles the radio', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('change', spy); getBaseElement(element).click(); diff --git a/libs/components/src/lib/range-slider/range-slider.spec.ts b/libs/components/src/lib/range-slider/range-slider.spec.ts index 3b7219c52c..12c5fd27cc 100644 --- a/libs/components/src/lib/range-slider/range-slider.spec.ts +++ b/libs/components/src/lib/range-slider/range-slider.spec.ts @@ -30,10 +30,8 @@ describe('vwc-range-slider', () => { } beforeEach(async () => { - jest - .spyOn(HTMLElement.prototype, 'clientWidth', 'get') - .mockReturnValue(1000); - jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(1000); + vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ bottom: 1000, top: 0, left: 0, @@ -661,7 +659,7 @@ describe('vwc-range-slider', () => { it.each(['input', 'change', 'input:start'])( 'should emit %s event only when the value changes', async (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); mouseDown(thumbs.start, 0); @@ -730,10 +728,10 @@ describe('vwc-range-slider', () => { // Cannot properly end-to-end test form value because jsdom does not support ElementInternals // Instead we mock the setFormValue method and test that it is called with the correct value const getFormValue = () => - jest.mocked(element.setFormValue).mock.lastCall![0] as FormData; + vi.mocked(element.setFormValue).mock.lastCall![0] as FormData; beforeEach(() => { - element.setFormValue = jest.fn(); + element.setFormValue = vi.fn(); }); it('should set the form value with name, start and end', () => { diff --git a/libs/components/src/lib/searchable-select/searchable-select.spec.ts b/libs/components/src/lib/searchable-select/searchable-select.spec.ts index e1509f943f..bf25874018 100644 --- a/libs/components/src/lib/searchable-select/searchable-select.spec.ts +++ b/libs/components/src/lib/searchable-select/searchable-select.spec.ts @@ -816,7 +816,7 @@ describe('vwc-searchable-select', () => { }); it('should ignore elided option tags when pressing ArrowLeft', async () => { - HTMLElement.prototype.getBoundingClientRect = jest.fn( + HTMLElement.prototype.getBoundingClientRect = vi.fn( () => ({ width: 100, @@ -880,7 +880,7 @@ describe('vwc-searchable-select', () => { resizeObserverDisconnected = true; } } as any; - HTMLElement.prototype.getBoundingClientRect = jest.fn(function () { + HTMLElement.prototype.getBoundingClientRect = vi.fn(function () { if (this.tagName === 'DIV') { return { width: currentWrapperWidth, @@ -992,9 +992,9 @@ describe('vwc-searchable-select', () => { }); describe.each(['input', 'change'])('%s event', (eventName) => { - let eventSpy: jest.Mock; + let eventSpy: vi.Mock; beforeEach(async () => { - eventSpy = jest.fn(); + eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); focusInput(); await elementUpdated(element); diff --git a/libs/components/src/lib/select/select.spec.ts b/libs/components/src/lib/select/select.spec.ts index 4d95e026aa..2ebb68b8f7 100644 --- a/libs/components/src/lib/select/select.spec.ts +++ b/libs/components/src/lib/select/select.spec.ts @@ -55,7 +55,7 @@ describe('vwc-select', () => { beforeAll(() => { originalScrollIntoView = HTMLElement.prototype.scrollIntoView; - HTMLElement.prototype.scrollIntoView = jest.fn(); + HTMLElement.prototype.scrollIntoView = vi.fn(); }); afterAll(() => { @@ -439,7 +439,7 @@ describe('vwc-select', () => { `; - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('input', spy); element.open = true; await elementUpdated(element); @@ -490,7 +490,7 @@ describe('vwc-select', () => { }); it('should allow propgation on escape key if not open', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); element.dispatchEvent( @@ -506,7 +506,7 @@ describe('vwc-select', () => { it('should stop propgation on escape key if open', async () => { element.open = true; - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); element.dispatchEvent( @@ -533,8 +533,8 @@ describe('vwc-select', () => { `; - const inputSpy = jest.fn(); - const changeSpy = jest.fn(); + const inputSpy = vi.fn(); + const changeSpy = vi.fn(); element.addEventListener('input', inputSpy); element.addEventListener('change', changeSpy); element.open = true; @@ -556,7 +556,11 @@ describe('vwc-select', () => { `; await elementUpdated(element); - (MouseEvent as any).prototype.offsetX = 0; + Object.defineProperty(MouseEvent.prototype, 'offsetX', { + get() { + return 0; + }, + }); element.dispatchEvent(new MouseEvent('mousedown')); const shouldSkipFocusAfterOneMouseDown = (element as any).shouldSkipFocus; @@ -589,7 +593,7 @@ describe('vwc-select', () => { describe('fixed-dropdown', () => { function setBoundingClientRect(width: number) { - element.getBoundingClientRect = jest.fn().mockReturnValue({ width }); + element.getBoundingClientRect = vi.fn().mockReturnValue({ width }); } async function toggleOpenState(open = true) { @@ -882,7 +886,7 @@ describe('vwc-select', () => { it.each(['input', 'change'])( `should emit %s event when selecting an option with the keyboard`, async (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); element.focus(); @@ -902,7 +906,7 @@ describe('vwc-select', () => { it.each(['input', 'change'])( `should emit %s event only once the select closes`, async (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); element.focus(); @@ -931,7 +935,7 @@ describe('vwc-select', () => { it.each(['input', 'change'])( `should emit %s event when selecting an option by clicking on it`, async (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); getOption('3').click(); @@ -1096,7 +1100,7 @@ describe('vwc-select', () => { it.each(['input', 'change'])( 'should emit %s event when toggling an option with the keyboard', async (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); element.focus(); @@ -1120,7 +1124,7 @@ describe('vwc-select', () => { it.each(['input', 'change'])( 'should emit %s event when toggling an option by clicking', (eventName) => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener(eventName, eventSpy); getOption('1').click(); @@ -1186,7 +1190,7 @@ describe('vwc-select', () => { getOption('1').click(); element.focus(); await elementUpdated(element); - getOption('1').scrollIntoView = jest.fn(); + getOption('1').scrollIntoView = vi.fn(); element.dispatchEvent(new KeyboardEvent('keydown', { key: keyTab })); await elementUpdated(element); @@ -1255,11 +1259,11 @@ describe('vwc-select', () => { element.focus(); await elementUpdated(element); - jest.useFakeTimers(); + vi.useFakeTimers(); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' })); - jest.advanceTimersByTime(5000); - jest.useRealTimers(); + vi.advanceTimersByTime(5000); + vi.useRealTimers(); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'n' })); diff --git a/libs/components/src/lib/selectable-box/selectable-box.spec.ts b/libs/components/src/lib/selectable-box/selectable-box.spec.ts index e4693d10eb..540cf23990 100644 --- a/libs/components/src/lib/selectable-box/selectable-box.spec.ts +++ b/libs/components/src/lib/selectable-box/selectable-box.spec.ts @@ -188,7 +188,7 @@ describe('vwc-selectable-box', () => { }); describe('change event', () => { - const spy = jest.fn(); + const spy = vi.fn(); let controlElement: any; beforeEach(async () => { @@ -198,7 +198,7 @@ describe('vwc-selectable-box', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('checkbox', () => { diff --git a/libs/components/src/lib/side-drawer/side-drawer.spec.ts b/libs/components/src/lib/side-drawer/side-drawer.spec.ts index b0737702f6..5c2caafa07 100644 --- a/libs/components/src/lib/side-drawer/side-drawer.spec.ts +++ b/libs/components/src/lib/side-drawer/side-drawer.spec.ts @@ -58,7 +58,7 @@ describe('vwc-side-drawer', () => { }); it('should fire open event', async function () { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('open', spy); element.open = true; await elementUpdated(element); @@ -66,7 +66,7 @@ describe('vwc-side-drawer', () => { }); it("should not bubble 'open' event", async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement?.addEventListener('open', spy); element.open = true; await elementUpdated(element); @@ -93,7 +93,7 @@ describe('vwc-side-drawer', () => { element.open = true; await elementUpdated(element); - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); element.open = false; await elementUpdated(element); @@ -104,7 +104,7 @@ describe('vwc-side-drawer', () => { it("should not bubble 'close' event", async () => { element.modal = true; element.open = true; - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement?.addEventListener('close', spy); element.open = false; @@ -211,7 +211,7 @@ describe('vwc-side-drawer', () => { }); it('should emit a non-bubbling event', async () => { - const onCancel = jest.fn(); + const onCancel = vi.fn(); element.parentElement!.addEventListener('cancel', onCancel); triggerCancelEvent(); @@ -230,7 +230,7 @@ describe('vwc-side-drawer', () => { }); it('should emit cancel event after clicking on scrim', async () => { - const cancelSpy = jest.fn(); + const cancelSpy = vi.fn(); element.addEventListener('cancel', cancelSpy); element.modal = true; element.open = true; @@ -256,7 +256,7 @@ describe('vwc-side-drawer', () => { }); it('should emit cancel after keydown on Escape', async () => { - const cancelSpy = jest.fn(); + const cancelSpy = vi.fn(); element.addEventListener('cancel', cancelSpy); control.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(cancelSpy).toHaveBeenCalledTimes(1); @@ -268,7 +268,7 @@ describe('vwc-side-drawer', () => { }); it('should stop propgation on escape key', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.parentElement!.addEventListener('keydown', spy); control.dispatchEvent( new KeyboardEvent('keydown', { @@ -283,7 +283,7 @@ describe('vwc-side-drawer', () => { it('should preventDefaut if Escape was pressed', async () => { const event = new KeyboardEvent('keydown', { key: 'Escape' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); control.dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(1); @@ -291,7 +291,7 @@ describe('vwc-side-drawer', () => { it('should enable default if key is not Escape', async () => { const event = new KeyboardEvent('keydown', { key: ' ' }); - jest.spyOn(event, 'preventDefault'); + vi.spyOn(event, 'preventDefault'); control.dispatchEvent(event); await elementUpdated(element); expect(event.preventDefault).toBeCalledTimes(0); diff --git a/libs/components/src/lib/slider/slider.spec.ts b/libs/components/src/lib/slider/slider.spec.ts index c969b133db..2a38996842 100644 --- a/libs/components/src/lib/slider/slider.spec.ts +++ b/libs/components/src/lib/slider/slider.spec.ts @@ -22,10 +22,8 @@ describe('vwc-slider', () => { element.shadowRoot!.querySelector('.popup') as Popup | null; beforeEach(async () => { - jest - .spyOn(HTMLElement.prototype, 'clientWidth', 'get') - .mockReturnValue(1000); - jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(1000); + vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ bottom: 1000, top: 0, left: 0, @@ -719,7 +717,7 @@ describe('vwc-slider', () => { }); it('should emit change event only when the value changes', async () => { - const eventSpy = jest.fn(); + const eventSpy = vi.fn(); element.addEventListener('change', eventSpy); mouseDown(thumb, 500); @@ -793,7 +791,7 @@ describe('vwc-slider', () => { describe('change event', () => { it('should fire a change event when value changes', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('change', spy); element.value = '0'; diff --git a/libs/components/src/lib/split-button/split-button.spec.ts b/libs/components/src/lib/split-button/split-button.spec.ts index 61421b9243..402fdc1b16 100644 --- a/libs/components/src/lib/split-button/split-button.spec.ts +++ b/libs/components/src/lib/split-button/split-button.spec.ts @@ -182,7 +182,7 @@ describe('vwc-split-button', () => { describe('action-click', () => { it('should fire a non-bubbling action-click event when action button is clicked', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('action-click', spy); element.action.click(); @@ -195,7 +195,7 @@ describe('vwc-split-button', () => { describe('indicator-click', () => { it('should fire a non-bubbling indicator-click event when indicator button is clicked', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('indicator-click', spy); element.indicator.click(); diff --git a/libs/components/src/lib/tab/tab.spec.ts b/libs/components/src/lib/tab/tab.spec.ts index e00808a691..9ed62e6f73 100644 --- a/libs/components/src/lib/tab/tab.spec.ts +++ b/libs/components/src/lib/tab/tab.spec.ts @@ -108,7 +108,7 @@ describe('vwc-tab', () => { const closeBtn = element.shadowRoot?.querySelector( '#close-btn' ) as HTMLButtonElement; - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); closeBtn?.click(); await elementUpdated(element); @@ -116,7 +116,7 @@ describe('vwc-tab', () => { }); it('should emit the close event when the delete key is pressed', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete' })); await elementUpdated(element); @@ -124,7 +124,7 @@ describe('vwc-tab', () => { }); it('should not emit the close event when another key is pressed', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('close', spy); element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Space' })); await elementUpdated(element); @@ -183,11 +183,11 @@ describe('vwc-tab', () => { describe('a11y', () => { it('should pass html a11y tests', async () => { - element = (await fixture( + const element = (await fixture( `
<${COMPONENT_TAG}>
` - )) as Tab; - element.label = 'Label'; - element.ariaSelected = 'true'; + )) as HTMLDivElement; + const tab = element.querySelector(COMPONENT_TAG) as Tab; + tab.label = 'Label'; await elementUpdated(element); expect(await axe(element)).toHaveNoViolations(); diff --git a/libs/components/src/lib/tabs/tabs.spec.ts b/libs/components/src/lib/tabs/tabs.spec.ts index 9335452aa8..0275a734b6 100644 --- a/libs/components/src/lib/tabs/tabs.spec.ts +++ b/libs/components/src/lib/tabs/tabs.spec.ts @@ -32,10 +32,10 @@ describe('vwc-tabs', () => { observe(target: HTMLElement) { this.observer = target; } - disconnect = jest.fn(() => { + disconnect = vi.fn(() => { this.observer = null; }); - unobserve = jest.fn(); + unobserve = vi.fn(); // Simulate resize event triggerResize() { if (this.observer) { @@ -55,8 +55,8 @@ describe('vwc-tabs', () => { left: 146, } as DOMRect; }; - window.HTMLElement.prototype.scrollIntoView = jest.fn(); - window.HTMLElement.prototype.scrollTo = jest.fn(); + window.HTMLElement.prototype.scrollIntoView = vi.fn(); + window.HTMLElement.prototype.scrollTo = vi.fn(); }); afterAll(() => { @@ -160,11 +160,11 @@ describe('vwc-tabs', () => { let shadowWrapper: HTMLElement; function setScrollWidth(value: number) { - jest.spyOn(scrollWrapper, 'scrollWidth', 'get').mockReturnValue(value); + vi.spyOn(scrollWrapper, 'scrollWidth', 'get').mockReturnValue(value); } function setClientWidth(value: number) { - jest.spyOn(scrollWrapper, 'clientWidth', 'get').mockReturnValue(value); + vi.spyOn(scrollWrapper, 'clientWidth', 'get').mockReturnValue(value); } async function dispatchScrollEvent() { @@ -320,20 +320,20 @@ describe('vwc-tabs', () => { }); describe('scrollToIndex', function () { - let scrollToSpy: jest.SpyInstance; + let scrollToSpy: vi.SpyInstance; const scrollWidth = 1320; const scrollHeight = 660; beforeEach(function () { const tablistWrapper = element.shadowRoot?.querySelector( '.tablist-wrapper' ) as HTMLElement; - jest - .spyOn(tablistWrapper, 'scrollWidth', 'get') - .mockImplementation(() => scrollWidth); - jest - .spyOn(tablistWrapper, 'scrollHeight', 'get') - .mockImplementation(() => scrollHeight); - scrollToSpy = jest.spyOn(tablistWrapper, 'scrollTo'); + vi.spyOn(tablistWrapper, 'scrollWidth', 'get').mockImplementation( + () => scrollWidth + ); + vi.spyOn(tablistWrapper, 'scrollHeight', 'get').mockImplementation( + () => scrollHeight + ); + scrollToSpy = vi.spyOn(tablistWrapper, 'scrollTo'); }); it('should scrollTo with 0 if first tab becomes active', async function () { @@ -390,18 +390,18 @@ describe('vwc-tabs', () => { const tablistWrapper = element.shadowRoot?.querySelector( '.tablist-wrapper' ) as HTMLElement; - jest - .spyOn(tablistWrapper, 'offsetWidth', 'get') - .mockImplementation(() => scrollWidth); + vi.spyOn(tablistWrapper, 'offsetWidth', 'get').mockImplementation( + () => scrollWidth + ); } function setMidTab(offsetLeft: number, offsetWidth: number) { const midTab = element.querySelectorAll('vwc-tab')[1] as Tab; - jest - .spyOn(midTab, 'offsetLeft', 'get') - .mockImplementation(() => offsetLeft); - jest - .spyOn(midTab, 'offsetWidth', 'get') - .mockImplementation(() => offsetWidth); + vi.spyOn(midTab, 'offsetLeft', 'get').mockImplementation( + () => offsetLeft + ); + vi.spyOn(midTab, 'offsetWidth', 'get').mockImplementation( + () => offsetWidth + ); return midTab; } @@ -425,18 +425,18 @@ describe('vwc-tabs', () => { const tablistWrapper = element.shadowRoot?.querySelector( '.tablist-wrapper' ) as HTMLElement; - jest - .spyOn(tablistWrapper, 'offsetHeight', 'get') - .mockImplementation(() => scrollHeight); + vi.spyOn(tablistWrapper, 'offsetHeight', 'get').mockImplementation( + () => scrollHeight + ); } function setMidTab(offsetLeft: number, offsetWidth: number) { const midTab = element.querySelectorAll('vwc-tab')[1] as Tab; - jest - .spyOn(midTab, 'offsetTop', 'get') - .mockImplementation(() => offsetLeft); - jest - .spyOn(midTab, 'offsetHeight', 'get') - .mockImplementation(() => offsetWidth); + vi.spyOn(midTab, 'offsetTop', 'get').mockImplementation( + () => offsetLeft + ); + vi.spyOn(midTab, 'offsetHeight', 'get').mockImplementation( + () => offsetWidth + ); return midTab; } diff --git a/libs/components/src/lib/tag/tag.spec.ts b/libs/components/src/lib/tag/tag.spec.ts index a9eedb8bf6..f4ed82e876 100644 --- a/libs/components/src/lib/tag/tag.spec.ts +++ b/libs/components/src/lib/tag/tag.spec.ts @@ -181,7 +181,7 @@ describe('vwc-tag', () => { }); it('should dispatch selected-changed', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.selected = true; await elementUpdated(element); @@ -300,7 +300,7 @@ describe('vwc-tag', () => { }); it('should fire removed event', async () => { - const spy = jest.fn(); + const spy = vi.fn(); await toggleRemovable(element, true); element.addEventListener('removed', spy); element.remove(); @@ -308,14 +308,14 @@ describe('vwc-tag', () => { }); it('should still show tag', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('removed', spy); element.remove(); expect(spy).not.toHaveBeenCalled(); }); it('should disable removed events after disconnected callback', async () => { - const spy = jest.fn(); + const spy = vi.fn(); element.addEventListener('removed', spy); element.disconnectedCallback(); diff --git a/libs/components/src/lib/text-area/text-area.spec.ts b/libs/components/src/lib/text-area/text-area.spec.ts index 51f98ddf81..7ed9bcec98 100644 --- a/libs/components/src/lib/text-area/text-area.spec.ts +++ b/libs/components/src/lib/text-area/text-area.spec.ts @@ -10,7 +10,7 @@ import { import { TextArea } from './text-area'; import '.'; -const COMPONENT_TAG_NAME = 'vwc-text-area'; +const COMPONENT_TAG = 'vwc-text-area'; describe('vwc-text-area', () => { function setToBlurred() { @@ -29,7 +29,7 @@ describe('vwc-text-area', () => { beforeEach(async () => { element = (await fixture( - `<${COMPONENT_TAG_NAME}>` + `<${COMPONENT_TAG}>` )) as TextArea; }); @@ -62,7 +62,7 @@ describe('vwc-text-area', () => { // createElement may fail even though indirect instantiation through innerHTML etc. succeeds // This is because only createElement performs checks for custom element constructor requirements // See https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance - expect(() => document.createElement(COMPONENT_TAG_NAME)).not.toThrow(); + expect(() => document.createElement(COMPONENT_TAG)).not.toThrow(); }); }); @@ -237,7 +237,7 @@ describe('vwc-text-area', () => { it('should attach to closest form', async function () { const { form: formElement } = createFormHTML