chore(core): type checking for unit tests (#28529)

Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Type checking inside of the Stencil unit tests have been disabled for a
long time. This has resulted in a difficult developer experience and
numerous issues (both types and implementation) within our unit tests.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Type checking is now enabled for all Stencil unit tests
- Tests have been updated to resolve type errors and implementation
errors
- Many `as any` casts were introduced, as many legacy tests test invalid
configurations of functions that require it (for example passing
`undefined` to an argument that cannot be `undefined`).

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

To test this PR you can checkout the branch locally. Install
dependencies in the `/core` directory to make sure you are on at least
`@stencil/core@4.7.2`.

Opening either a `.spec.ts` or `.spec.tsx` file, validate that your IDE
detects types and can provide auto completions for jest global types.

If you desire, you can provide an invalid type and try building the
project - you will observe the build will fail due to the invalid type.
This commit is contained in:
Sean Perkins
2023-11-17 11:47:34 -05:00
committed by GitHub
parent 1a135ebd76
commit 4f1b4cdc29
48 changed files with 2088 additions and 409 deletions

View File

@@ -108,7 +108,7 @@ describe('Animation Class', () => {
animation.play();
animation.progressStart();
animation.progressEnd(1);
animation.progressEnd(1, 0);
expect(animation.isRunning()).toEqual(true);
});
@@ -125,9 +125,9 @@ describe('Animation Class', () => {
await animation.play();
animation.progressStart();
animation.progressEnd(0);
animation.progressEnd(0, 0);
await new Promise((resolve) => {
await new Promise<void>((resolve) => {
animation.onFinish(() => {
expect(animation.isRunning()).toEqual(false);
resolve();
@@ -161,8 +161,8 @@ describe('Animation Class', () => {
const el = document.createElement('p');
animation.addElement(el);
animation.addElement(null);
animation.addElement(undefined);
animation.addElement(null as any);
animation.addElement(undefined as any);
expect(animation.elements.length).toEqual(1);
});
@@ -188,8 +188,8 @@ describe('Animation Class', () => {
});
it('should not error when trying to add null or undefined', () => {
animation.addAnimation(null);
animation.addAnimation(undefined);
animation.addAnimation(null as any);
animation.addAnimation(undefined as any);
expect(animation.childAnimations.length).toEqual(0);
});
@@ -312,7 +312,7 @@ describe('Animation Class', () => {
animation.progressStart(true);
expect(animation.getEasing()).toEqual('linear');
animation.progressEnd();
animation.progressEnd(0, 0);
expect(animation.getEasing()).toEqual('ease-in-out');
});
@@ -428,9 +428,15 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), [0.16]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), [0.56]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), [0.11]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.5), [
0.16,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.97), [
0.56,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.33), [
0.11,
]);
});
it('cubic-bezier(1, 0, 0.68, 0.28)', () => {
@@ -441,9 +447,15 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), [0.6]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), [0.84]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), [0.98]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.08), [
0.6,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.5), [
0.84,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.94), [
0.98,
]);
});
it('cubic-bezier(0.4, 0, 0.6, 1)', () => {
@@ -454,9 +466,15 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), [0.43]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), [0.11]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), [0.78]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.39), [
0.43,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.03), [
0.11,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.89), [
0.78,
]);
});
it('cubic-bezier(0, 0, 0.2, 1)', () => {
@@ -467,9 +485,15 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), [0.71]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), [0.03]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.7), [0.35]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.95), [
0.71,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.1), [
0.03,
]);
shouldApproximatelyEqual(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 0.7), [
0.35,
]);
});
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
@@ -480,8 +504,8 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
expect(getTimeGivenProgression(...equation, 1.32)[0]).toBeUndefined();
expect(getTimeGivenProgression(...equation, -0.32)[0]).toBeUndefined();
expect(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 1.32)[0]).toBeUndefined();
expect(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], -0.32)[0]).toBeUndefined();
});
it('cubic-bezier(0.21, 1.71, 0.88, 0.9) (multiple solutions)', () => {
@@ -492,7 +516,10 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 1.02), [0.35, 0.87]);
shouldApproximatelyEqual(
getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 1.02),
[0.35, 0.87]
);
});
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
@@ -503,8 +530,8 @@ describe('cubic-bezier conversion', () => {
[1, 1],
];
expect(getTimeGivenProgression(...equation, 1.32)).toEqual([]);
expect(getTimeGivenProgression(...equation, -0.32)).toEqual([]);
expect(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], 1.32)).toEqual([]);
expect(getTimeGivenProgression(equation[0], equation[1], equation[2], equation[3], -0.32)).toEqual([]);
});
});
});

View File

@@ -16,8 +16,8 @@ const mockVisualViewport = (
win: Window,
visualViewport: any = { width: 320, height: 568 },
layoutViewport = { innerWidth: 320, innerHeight: 568 }
): any => {
win.visualViewport = {
) => {
(win as any).visualViewport = {
width: 320,
height: 568,
offsetTop: 0,
@@ -29,26 +29,32 @@ const mockVisualViewport = (
onscroll: undefined,
};
win.visualViewport = Object.assign(win.visualViewport, visualViewport);
(win as any).visualViewport = Object.assign(win.visualViewport!, visualViewport);
win = Object.assign(win, layoutViewport);
win.dispatchEvent = jest.fn();
const mockDispatchEvent = jest.fn();
win.dispatchEvent = mockDispatchEvent;
trackViewportChanges(win);
return win;
return {
win,
mockDispatchEvent,
};
};
const mockCapacitor = (win: Window) => {
win.Capacitor = {
(win as any).Capacitor = {
isPluginAvailable: () => false,
};
};
const resizeVisualViewport = (win: Window, visualViewport: any = {}) => {
win.visualViewport = Object.assign(win.visualViewport, visualViewport);
(win as any).visualViewport = Object.assign((win as any).visualViewport, visualViewport);
if (win.visualViewport.onresize) {
win.visualViewport.onresize();
if (win.visualViewport!.onresize) {
win.visualViewport!.onresize({} as any);
} else {
trackViewportChanges(win);
}
@@ -87,62 +93,64 @@ describe('Keyboard Assist Tests', () => {
describe('setKeyboardOpen()', () => {
it('should dispatch the keyboard open event on the window', () => {
window.dispatchEvent = jest.fn();
const mockDispatchEvent = jest.fn();
window.dispatchEvent = mockDispatchEvent;
setKeyboardOpen(window);
expect(window.dispatchEvent.mock.calls.length).toEqual(1);
expect(window.dispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(mockDispatchEvent.mock.calls.length).toEqual(1);
expect(mockDispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
});
});
describe('setKeyboardClose()', () => {
it('should dispatch the keyboard close event on the window', () => {
window.dispatchEvent = jest.fn();
const mockDispatchEvent = jest.fn();
window.dispatchEvent = mockDispatchEvent;
setKeyboardClose(window);
expect(window.dispatchEvent.mock.calls.length).toEqual(1);
expect(window.dispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_CLOSE);
expect(mockDispatchEvent.mock.calls.length).toEqual(1);
expect(mockDispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_CLOSE);
});
});
describe('keyboardDidOpen()', () => {
beforeEach(() => {
resetKeyboardAssist(window);
resetKeyboardAssist();
mockVisualViewport(window);
});
it('should return true when visual viewport height < layout viewport height and meets or exceeds the keyboard threshold', () => {
resizeVisualViewport(window, { height: 200 });
expect(keyboardDidOpen(window)).toEqual(true);
expect(keyboardDidOpen()).toEqual(true);
});
it('should return true if the layout and visual viewports resize', () => {
resizeLayoutViewport(window, { width: 320, height: 300 });
resizeVisualViewport(window, { width: 320, height: 300 });
expect(keyboardDidOpen(window)).toEqual(true);
expect(keyboardDidOpen()).toEqual(true);
});
it('should return false when visual viewport height < layout viewport heigh but does not meet the keyboard threshold', () => {
resizeVisualViewport(window, { height: 500 });
expect(keyboardDidOpen(window)).toEqual(false);
expect(keyboardDidOpen()).toEqual(false);
});
it('should return false on orientation change', () => {
resizeVisualViewport(window, { width: 320, height: 250 });
resizeVisualViewport(window, { width: 250, height: 320 });
expect(keyboardDidOpen(window)).toEqual(false);
expect(keyboardDidOpen()).toEqual(false);
});
it('should return false when both the visual and layout viewports change', () => {
resizeVisualViewport(window, { width: 250, height: 320 }, { innerWidth: 250, innerHeight: 320 });
resizeVisualViewport(window, { width: 250, height: 320 });
expect(keyboardDidOpen(window)).toEqual(false);
expect(keyboardDidOpen()).toEqual(false);
});
it('should return true when the keyboard shows even if the user is zoomed in', () => {
@@ -152,13 +160,13 @@ describe('Keyboard Assist Tests', () => {
// User taps input and keyboard appears
resizeVisualViewport(window, { width: 160, height: 184, scale: 2 });
expect(keyboardDidOpen(window)).toEqual(true);
expect(keyboardDidOpen()).toEqual(true);
});
});
describe('keyboardDidClose()', () => {
beforeEach(() => {
resetKeyboardAssist(window);
resetKeyboardAssist();
mockVisualViewport(window);
});
@@ -222,54 +230,66 @@ describe('Keyboard Assist Tests', () => {
});
describe('Keyboard Assist Integration', () => {
let mockDispatchEvent: jest.Mock<any, any>;
beforeEach(() => {
resetKeyboardAssist(window);
mockVisualViewport(window);
resetKeyboardAssist();
mockDispatchEvent = mockVisualViewport(window).mockDispatchEvent;
startKeyboardAssist(window);
});
afterEach(() => {
mockDispatchEvent.mockReset();
});
it('should properly set the keyboard to be open', () => {
resizeVisualViewport(window, { width: 320, height: 350 });
expect(window.dispatchEvent.mock.calls.length).toEqual(1);
expect(window.dispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(mockDispatchEvent.mock.calls.length).toEqual(1);
expect(mockDispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
});
it('should properly set the keyboard to be closed', () => {
resizeVisualViewport(window, { width: 320, height: 350 });
resizeVisualViewport(window, { width: 320, height: 568 });
expect(window.dispatchEvent.mock.calls.length).toEqual(2);
expect(window.dispatchEvent.mock.calls[1][0].type).toEqual(KEYBOARD_DID_CLOSE);
expect(mockDispatchEvent.mock.calls.length).toEqual(2);
expect(mockDispatchEvent.mock.calls[1][0].type).toEqual(KEYBOARD_DID_CLOSE);
});
it('should properly set the keyboard to be resized', () => {
resizeVisualViewport(window, { width: 320, height: 350 });
resizeVisualViewport(window, { width: 320, height: 360 });
expect(window.dispatchEvent.mock.calls.length).toEqual(2);
expect(window.dispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(window.dispatchEvent.mock.calls[1][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(mockDispatchEvent.mock.calls.length).toEqual(2);
expect(mockDispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(mockDispatchEvent.mock.calls[1][0].type).toEqual(KEYBOARD_DID_OPEN);
});
it('should not set keyboard open on orientation change', () => {
resizeVisualViewport(window, { width: 568, height: 320 });
expect(window.dispatchEvent.mock.calls.length).toEqual(0);
expect(mockDispatchEvent.mock.calls.length).toEqual(0);
});
});
describe('Keyboard Assist with Capacitor', () => {
let mockDispatchEvent: jest.Mock<any, any>;
beforeEach(() => {
resetKeyboardAssist(window);
resetKeyboardAssist();
mockCapacitor(window);
mockVisualViewport(window);
mockDispatchEvent = mockVisualViewport(window).mockDispatchEvent;
startKeyboardAssist(window);
});
afterEach(() => {
mockDispatchEvent.mockReset();
});
it('should attach visual viewport listeners when Capacitor is available but the Keyboard plugin is not', () => {
resizeVisualViewport(window, { width: 320, height: 350 });
expect(window.dispatchEvent.mock.calls.length).toEqual(1);
expect(window.dispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
expect(mockDispatchEvent.mock.calls.length).toEqual(1);
expect(mockDispatchEvent.mock.calls[0][0].type).toEqual(KEYBOARD_DID_OPEN);
});
});

View File

@@ -65,7 +65,7 @@ describe('sanitizeDOMString', () => {
});
const enableSanitizer = (enable = true) => {
window.Ionic = {};
window.Ionic.config = {};
window.Ionic.config.sanitizerEnabled = enable;
(window as any).Ionic = {};
(window as any).Ionic.config = {};
(window as any).Ionic.config.sanitizerEnabled = enable;
};

View File

@@ -1,8 +1,8 @@
import { newSpecPage } from '@stencil/core/testing';
import { Item } from '../../components/item/item.tsx';
import { Label } from '../../components/label/label.tsx';
import { Toggle } from '../../components/toggle/toggle.tsx';
import { Item } from '../../components/item/item';
import { Label } from '../../components/label/label';
import { Toggle } from '../../components/toggle/toggle';
import { getAriaLabel } from '../helpers';
describe('getAriaLabel()', () => {
@@ -17,7 +17,7 @@ describe('getAriaLabel()', () => {
`,
});
const toggle = page.body.querySelector('ion-toggle');
const toggle = page.body.querySelector('ion-toggle')!;
const { label, labelId, labelText } = getAriaLabel(toggle, 'ion-tg-0');
@@ -35,7 +35,7 @@ describe('getAriaLabel()', () => {
`,
});
const toggle = page.body.querySelector('ion-toggle');
const toggle = page.body.querySelector('ion-toggle')!;
const { label, labelId, labelText } = getAriaLabel(toggle, 'ion-tg-0');
@@ -53,7 +53,7 @@ describe('getAriaLabel()', () => {
`,
});
const toggle = page.body.querySelector('ion-toggle');
const toggle = page.body.querySelector('ion-toggle')!;
const { labelId, labelText } = getAriaLabel(toggle, 'ion-tg-0');
@@ -70,7 +70,7 @@ describe('getAriaLabel()', () => {
`,
});
const toggle = page.body.querySelector('ion-toggle');
const toggle = page.body.querySelector('ion-toggle')!;
const { labelId, labelText } = getAriaLabel(toggle, 'ion-tg-0');
@@ -87,7 +87,7 @@ describe('getAriaLabel()', () => {
`,
});
const toggle = page.body.querySelector('ion-toggle');
const toggle = page.body.querySelector('ion-toggle')!;
const { labelId, labelText } = getAriaLabel(toggle, 'ion-tg-0');

View File

@@ -1,3 +1,4 @@
import type { BackButtonEvent } from '../../../src/interface';
import { startHardwareBackButton } from '../hardware-back-button';
describe('Hardware Back Button', () => {
@@ -5,7 +6,7 @@ describe('Hardware Back Button', () => {
it('should call handler', () => {
const cbSpy = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(0, cbSpy);
(ev as BackButtonEvent).detail.register(0, cbSpy);
});
dispatchBackButtonEvent();
@@ -16,8 +17,8 @@ describe('Hardware Back Button', () => {
const cbSpy = jest.fn();
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(99, cbSpyTwo);
(ev as BackButtonEvent).detail.register(100, cbSpy);
(ev as BackButtonEvent).detail.register(99, cbSpyTwo);
});
dispatchBackButtonEvent();
@@ -29,8 +30,8 @@ describe('Hardware Back Button', () => {
const cbSpy = jest.fn();
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(100, cbSpyTwo);
(ev as BackButtonEvent).detail.register(100, cbSpy);
(ev as BackButtonEvent).detail.register(100, cbSpyTwo);
});
dispatchBackButtonEvent();
@@ -39,13 +40,13 @@ describe('Hardware Back Button', () => {
});
it('should call multiple callbacks', () => {
const cbSpy = (processNextHandler) => {
const cbSpy = (processNextHandler: () => void) => {
processNextHandler();
};
const cbSpyTwo = jest.fn();
document.addEventListener('ionBackButton', (ev) => {
ev.detail.register(100, cbSpy);
ev.detail.register(99, cbSpyTwo);
(ev as BackButtonEvent).detail.register(100, cbSpy);
(ev as BackButtonEvent).detail.register(99, cbSpyTwo);
});
dispatchBackButtonEvent();

View File

@@ -1,9 +1,8 @@
import { newSpecPage } from '@stencil/core/testing';
import { Modal } from '../../../components/modal/modal';
import { Nav } from '../../../components/nav/nav';
import { RouterOutlet } from '../../../components/router-outlet/router-outlet';
import { Modal } from '../../../components/modal/modal';
import { setRootAriaHidden } from '../../overlays';
describe('setRootAriaHidden()', () => {
@@ -15,7 +14,7 @@ describe('setRootAriaHidden()', () => {
`,
});
const routerOutlet = page.body.querySelector('ion-router-outlet');
const routerOutlet = page.body.querySelector('ion-router-outlet')!;
expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
@@ -34,7 +33,7 @@ describe('setRootAriaHidden()', () => {
`,
});
const nav = page.body.querySelector('ion-nav');
const nav = page.body.querySelector('ion-nav')!;
expect(nav.hasAttribute('aria-hidden')).toEqual(false);
@@ -54,8 +53,8 @@ describe('setRootAriaHidden()', () => {
`,
});
const containerRoot = page.body.querySelector('#ion-view-container-root');
const notContainerRoot = page.body.querySelector('#not-container-root');
const containerRoot = page.body.querySelector('#ion-view-container-root')!;
const notContainerRoot = page.body.querySelector('#not-container-root')!;
expect(containerRoot.hasAttribute('aria-hidden')).toEqual(false);
expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
@@ -90,8 +89,8 @@ describe('setRootAriaHidden()', () => {
`,
});
const routerOutlet = page.body.querySelector('ion-router-outlet');
const modal = page.body.querySelector('ion-modal');
const routerOutlet = page.body.querySelector('ion-router-outlet')!;
const modal = page.body.querySelector('ion-modal')!;
await modal.present();
@@ -109,9 +108,9 @@ describe('setRootAriaHidden()', () => {
`,
});
const routerOutlet = page.body.querySelector('ion-router-outlet');
const modalOne = page.body.querySelector('ion-modal#one');
const modalTwo = page.body.querySelector('ion-modal#two');
const routerOutlet = page.body.querySelector('ion-router-outlet')!;
const modalOne = page.body.querySelector<HTMLIonModalElement>('ion-modal#one')!;
const modalTwo = page.body.querySelector<HTMLIonModalElement>('ion-modal#two')!;
await modalOne.present();

View File

@@ -12,7 +12,7 @@ describe('componentOnReady()', () => {
);
const component = document.createElement('hello-world');
componentOnReady(component, (el) => {
componentOnReady(component, (el: HTMLElement) => {
expect(el).toBe(component);
done();
});
@@ -39,7 +39,7 @@ describe('componentOnReady()', () => {
);
const component = document.createElement('hello-world');
componentOnReady(component, (el) => {
componentOnReady(component, (el: HTMLElement) => {
expect(el).toBe(component);
expect(cb).toHaveBeenCalledTimes(1);
done();