diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index b034aa071f9..c1e9b0c0d2d 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -18,11 +18,12 @@ "clean": "node -e \"fs.rmSync('lib', { recursive: true, force: true })\" || true", "precompile": "npm run clean", "compile": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", "eslint": "eslint-compass", "prettier": "prettier-compass", "lint": "npm run eslint . && npm run prettier -- --check .", "depcheck": "compass-scripts check-peer-deps && depcheck", - "check": "npm run lint && npm run depcheck", + "check": "npm run typecheck && npm run lint && npm run depcheck", "check-ci": "npm run check", "test": "mocha", "test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", diff --git a/packages/compass-components/src/components/content-with-fallback.spec.tsx b/packages/compass-components/src/components/content-with-fallback.spec.tsx index dc03bba9dd3..7c2e0b3a74b 100644 --- a/packages/compass-components/src/components/content-with-fallback.spec.tsx +++ b/packages/compass-components/src/components/content-with-fallback.spec.tsx @@ -59,7 +59,9 @@ describe('ContentWithFallback', function () { ); expect(container.children.length).to.equal(2); - const [contentContainer, contextMenuContainer] = container.children; + const [contentContainer, contextMenuContainer] = Array.from( + container.children + ); expect(contentContainer.children.length).to.equal(0); expect(contextMenuContainer.getAttribute('data-testid')).to.equal( 'context-menu-container' diff --git a/packages/compass-components/src/components/document-list/document.spec.tsx b/packages/compass-components/src/components/document-list/document.spec.tsx index 974c7813b6f..c66f5bcfbc3 100644 --- a/packages/compass-components/src/components/document-list/document.spec.tsx +++ b/packages/compass-components/src/components/document-list/document.spec.tsx @@ -11,7 +11,7 @@ import { import HadronDocument from 'hadron-document'; import Document from './document'; -const EditableDoc = ({ doc }) => { +const EditableDoc = ({ doc }: { doc: HadronDocument }) => { const [editing, setEditing] = useState(false); return ( @@ -61,8 +61,11 @@ describe('Document', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('str').uuid}"]` + `[data-id="${doc.get('str')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const keyEditor = within(el).getByTestId('hadron-document-key-editor'); userEvent.clear(keyEditor); @@ -71,16 +74,19 @@ describe('Document', function () { expect(screen.getByDisplayValue('new_name')).to.exist; - expect(doc.get('new_name').key).to.eq('str'); - expect(doc.get('new_name').currentKey).to.eq('new_name'); + expect(doc.get('new_name')?.key).to.eq('str'); + expect(doc.get('new_name')?.currentKey).to.eq('new_name'); }); it('should change element string value on edit', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('str').uuid}"]` + `[data-id="${doc.get('str')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const valueEditor = within(el).getByTestId( 'hadron-document-value-editor' @@ -90,16 +96,19 @@ describe('Document', function () { userEvent.keyboard('bla'); userEvent.tab(); - expect(doc.get('str').currentValue).to.eq('bla'); - expect(doc.get('str').currentType).to.eq('String'); + expect(doc.get('str')?.currentValue).to.eq('bla'); + expect(doc.get('str')?.currentType).to.eq('String'); }); it('should change element number value on edit', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('num').uuid}"]` + `[data-id="${doc.get('num')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const valueEditor = within(el).getByTestId( 'hadron-document-value-editor' @@ -109,16 +118,19 @@ describe('Document', function () { userEvent.keyboard('321'); userEvent.tab(); - expect(doc.get('num').currentValue.valueOf()).to.eq(321); - expect(doc.get('num').currentType).to.eq('Int32'); + expect(doc.get('num')?.currentValue?.valueOf()).to.eq(321); + expect(doc.get('num')?.currentType).to.eq('Int32'); }); it('should change element date value on edit', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('date').uuid}"]` + `[data-id="${doc.get('date')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const valueEditor = within(el).getByTestId( 'hadron-document-value-editor' @@ -128,26 +140,29 @@ describe('Document', function () { userEvent.keyboard('2000-01-01'); userEvent.tab(); - expect((doc.get('date').currentValue as Date).toISOString()).to.eq( + expect((doc.get('date')?.currentValue as Date).toISOString()).to.eq( '2000-01-01T00:00:00.000Z' ); - expect(doc.get('date').currentType).to.eq('Date'); + expect(doc.get('date')?.currentType).to.eq('Date'); }); it('should change element type on edit', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('num').uuid}"]` + `[data-id="${doc.get('num')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const typeEditor = within(el).getByTestId('hadron-document-type-editor'); userEvent.selectOptions(typeEditor, 'String'); userEvent.tab(); - expect(doc.get('num').currentValue.valueOf()).to.eq('123'); - expect(doc.get('num').currentType).to.eq('String'); + expect(doc.get('num')?.currentValue?.valueOf()).to.eq('123'); + expect(doc.get('num')?.currentType).to.eq('String'); }); }); @@ -168,8 +183,11 @@ describe('Document', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('null_value').uuid}"]` + `[data-id="${doc.get('null_value')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } const typeEditor = within(el).getByTestId('hadron-document-type-editor'); @@ -182,16 +200,19 @@ describe('Document', function () { userEvent.keyboard('foo bar'); userEvent.tab(); - expect(doc.get('null_value').currentValue.valueOf()).to.eq('foo bar'); - expect(doc.get('null_value').currentType).to.eq('String'); + expect(doc.get('null_value')?.currentValue?.valueOf()).to.eq('foo bar'); + expect(doc.get('null_value')?.currentType).to.eq('String'); }); it('should autofocus key editor when double-clicking key', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('str').uuid}"]` + `[data-id="${doc.get('str')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } userEvent.dblClick(within(el).getByTestId('hadron-document-clickable-key')); @@ -204,8 +225,11 @@ describe('Document', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('str').uuid}"]` + `[data-id="${doc.get('str')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } userEvent.dblClick( within(el).getByTestId('hadron-document-clickable-value') @@ -220,8 +244,11 @@ describe('Document', function () { render(); const el = document.querySelector( - `[data-id="${doc.get('null_value').uuid}"]` + `[data-id="${doc.get('null_value')?.uuid}"]` ); + if (!el) { + throw new Error('Could not find element'); + } userEvent.dblClick( within(el).getByTestId('hadron-document-clickable-value') diff --git a/packages/compass-components/src/components/file-picker-dialog.spec.tsx b/packages/compass-components/src/components/file-picker-dialog.spec.tsx index 40ce148a4ed..8a957895d89 100644 --- a/packages/compass-components/src/components/file-picker-dialog.spec.tsx +++ b/packages/compass-components/src/components/file-picker-dialog.spec.tsx @@ -17,7 +17,7 @@ import FileInput, { } from './file-picker-dialog'; describe('FileInput', function () { - let spy; + let spy: sinon.SinonSpy; beforeEach(function () { spy = sinon.spy(); diff --git a/packages/compass-components/src/components/guide-cue/guide-cue-service.spec.ts b/packages/compass-components/src/components/guide-cue/guide-cue-service.spec.ts index f090ca0bcd4..625ef8450d7 100644 --- a/packages/compass-components/src/components/guide-cue/guide-cue-service.spec.ts +++ b/packages/compass-components/src/components/guide-cue/guide-cue-service.spec.ts @@ -397,26 +397,31 @@ describe('GuideCueService', function () { context('marks group as visited', function () { context('when all the cues of group are added', function () { - const cue1 = { - cueId: 'one', - step: 1, - groupId: 'group-two', - isIntersecting: true, - } as unknown as Cue; - const cue2 = { - cueId: 'two', - step: 2, - groupId: 'group-two', - isIntersecting: true, - }; + let cue1: Cue; + let cue2: Cue; let markCueAsVisited: Sinon.SinonSpy; beforeEach(function () { + cue1 = { + cueId: 'one', + step: 1, + groupId: 'group-two', + isVisited: false, + isIntersecting: true, + }; + cue2 = { + cueId: 'two', + step: 2, + groupId: 'group-two', + isVisited: false, + isIntersecting: true, + }; + markCueAsVisited = Sinon.spy(guideCueStorage, 'markCueAsVisited'); - guideCueService.addCue(cue1 as any); - guideCueService.addCue(cue2 as any); - guideCueService.markGroupAsVisited(cue1.groupId); + guideCueService.addCue(cue1); + guideCueService.addCue(cue2); + guideCueService.markGroupAsVisited(cue1.groupId!); }); it('updates isVisited property for all group cues', function () { @@ -444,19 +449,21 @@ describe('GuideCueService', function () { }); context('when all the cues of group are not added', function () { - const cue1 = { - cueId: 'one', - step: 1, - groupId: 'group-two', - isIntersecting: true, - } as unknown as Cue; + let cue1: Cue; let markCueAsVisited: Sinon.SinonSpy; beforeEach(function () { + cue1 = { + cueId: 'one', + step: 1, + groupId: 'group-two', + isIntersecting: true, + isVisited: false, + }; markCueAsVisited = Sinon.spy(guideCueStorage, 'markCueAsVisited'); - guideCueService.addCue(cue1 as any); - guideCueService.markGroupAsVisited(cue1.groupId); + guideCueService.addCue(cue1); + guideCueService.markGroupAsVisited(cue1.groupId!); }); it('does not update isVisited property for group cues', function () { diff --git a/packages/compass-components/src/components/guide-cue/guide-cue.spec.tsx b/packages/compass-components/src/components/guide-cue/guide-cue.spec.tsx index 59b2a799c49..2c52cad8f29 100644 --- a/packages/compass-components/src/components/guide-cue/guide-cue.spec.tsx +++ b/packages/compass-components/src/components/guide-cue/guide-cue.spec.tsx @@ -25,6 +25,8 @@ const renderGuideCue = (props: Partial>) => { ( diff --git a/packages/compass-components/src/components/interactive-popover.spec.tsx b/packages/compass-components/src/components/interactive-popover.spec.tsx index defecead7da..72e4052e7f9 100644 --- a/packages/compass-components/src/components/interactive-popover.spec.tsx +++ b/packages/compass-components/src/components/interactive-popover.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, cleanup } from '@mongodb-js/testing-library-compass'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; @@ -17,9 +17,22 @@ function renderPopover( hideCloseButton={props?.hideCloseButton} customFocusTrapFallback={`#${innerContentTestId}`} setOpen={() => {}} - trigger={({ onClick, ref, children }) => ( + trigger={({ + onClick, + ref, + children, + }: { + onClick: React.MouseEventHandler; + ref: React.Ref; + children?: React.ReactNode; + }) => ( <> - {children} @@ -43,8 +56,15 @@ function renderPopover( } describe('InteractivePopover Component', function () { + let addedElement: HTMLDivElement | undefined; afterEach(function () { - cleanup(); + if (addedElement) { + try { + document.body.removeChild(addedElement); + } finally { + addedElement = undefined; + } + } }); it('when open it should show the popover content', function () { @@ -124,4 +144,84 @@ describe('InteractivePopover Component', function () { expect(screen.queryByTestId('interactive-popover-close-button')).to.not .exist; }); + + it('should close when escape key is pressed', function () { + const openSpy = sinon.fake(); + + renderPopover({ + open: true, + setOpen: openSpy, + }); + + expect(openSpy.calledOnce).to.be.false; + userEvent.keyboard('{Escape}'); + expect(openSpy.calledOnce).to.be.true; + expect(openSpy.firstCall.firstArg).to.equal(false); + }); + + it('should close when clicking outside the popover', function () { + const openSpy = sinon.fake(); + + renderPopover({ + open: true, + setOpen: openSpy, + }); + + addedElement = document.createElement('div'); + document.body.appendChild(addedElement); + + expect(openSpy.calledOnce).to.be.false; + userEvent.click(addedElement); + + expect(openSpy.calledOnce).to.be.true; + expect(openSpy.firstCall.firstArg).to.equal(false); + }); + + it('should not close when clicking on contained elements', function () { + const openSpy = sinon.fake(); + + addedElement = document.createElement('div'); + addedElement.className = 'contained-element'; + document.body.appendChild(addedElement); + + renderPopover({ + open: true, + setOpen: openSpy, + containedElements: ['.contained-element'], + }); + + userEvent.click(addedElement); + expect(openSpy.called).to.be.false; + }); + + it('should not close when clicking inside the popover content', function () { + const openSpy = sinon.fake(); + + renderPopover({ + open: true, + setOpen: openSpy, + }); + + const innerButton = screen.getByTestId(innerContentTestId); + userEvent.click(innerButton); + + expect(openSpy.called).to.be.false; + }); + + it('should focus the trigger after closing the popover', function (done) { + const openSpy = sinon.fake(); + + renderPopover({ + open: true, + setOpen: openSpy, + }); + + const triggerButton = screen.getByTestId('trigger-button'); + triggerButton.addEventListener('focus', () => { + done(); + }); + + const closeButton = screen.getByTestId('interactive-popover-close-button'); + closeButton.click(); + }); }); diff --git a/packages/compass-components/src/components/list-editor.spec.tsx b/packages/compass-components/src/components/list-editor.spec.tsx index 7a37c80afc8..a03c9768781 100644 --- a/packages/compass-components/src/components/list-editor.spec.tsx +++ b/packages/compass-components/src/components/list-editor.spec.tsx @@ -50,8 +50,8 @@ describe('ListEditor', function () { }); describe('when rendered with multiple items', function () { - let onAddItemSpy; - let onRemoveItemSpy; + let onAddItemSpy: sinon.SinonSpy; + let onRemoveItemSpy: sinon.SinonSpy; beforeEach(function () { onAddItemSpy = sinon.spy(); diff --git a/packages/compass-components/src/components/loader.spec.tsx b/packages/compass-components/src/components/loader.spec.tsx index 4c381b6a3b4..3c099df0332 100644 --- a/packages/compass-components/src/components/loader.spec.tsx +++ b/packages/compass-components/src/components/loader.spec.tsx @@ -13,7 +13,7 @@ function renderLoader() { return render(); } -function renderCancelLoader(spy) { +function renderCancelLoader(spy: sinon.SinonSpy) { return render( useSortedItems(items, result.current[1])); + } = renderHook(() => + useSortedItems(items as Record[], result.current[1]) + ); expect(sortedItems).to.deep.equal([items[0], items[1]]); }); @@ -100,7 +102,9 @@ describe('use-sort', function () { const { result: { current: sortedItems }, - } = renderHook(() => useSortedItems(items, result.current[1])); + } = renderHook(() => + useSortedItems(items as Record[], result.current[1]) + ); expect(sortedItems).to.deep.equal([items[1], items[0]]); }); @@ -130,7 +134,9 @@ describe('use-sort', function () { const { result: { current: sortedItems }, - } = renderHook(() => useSortedItems(items, result.current[1])); + } = renderHook(() => + useSortedItems(items as Record[], result.current[1]) + ); expect(sortedItems).to.deep.equal([items[1], items[0]]); }); @@ -162,7 +168,9 @@ describe('use-sort', function () { const { result: { current: sortedItems }, - } = renderHook(() => useSortedItems(items, result.current[1])); + } = renderHook(() => + useSortedItems(items as Record[], result.current[1]) + ); expect(sortedItems).to.deep.equal([items[0], items[1]]); }); @@ -174,7 +182,9 @@ describe('use-sort', function () { const { result: { current: sortedItems }, - } = renderHook(() => useSortedItems(items, result.current[1])); + } = renderHook(() => + useSortedItems(items as Record[], result.current[1]) + ); expect(sortedItems).to.deep.equal(items); }); }); diff --git a/packages/compass-components/src/hooks/use-toast.spec.tsx b/packages/compass-components/src/hooks/use-toast.spec.tsx index c0f14df415c..8d110a153ba 100644 --- a/packages/compass-components/src/hooks/use-toast.spec.tsx +++ b/packages/compass-components/src/hooks/use-toast.spec.tsx @@ -10,6 +10,8 @@ import React from 'react'; import { ToastArea, openToast, closeToast } from './use-toast'; import type { ToastProperties } from './use-toast'; +const toastId = (namespace: string, id: string) => `${namespace}--${id}`; + const OpenToastButton = ({ namespace, id, @@ -22,7 +24,7 @@ const OpenToastButton = ({