diff --git a/src/hooks/useAsyncValue.ts b/src/hooks/useAsyncValue.ts index a71b836d..63114ce1 100644 --- a/src/hooks/useAsyncValue.ts +++ b/src/hooks/useAsyncValue.ts @@ -5,8 +5,8 @@ import type { DependencyList } from 'react'; -import { useInstantEffect } from './useInstantEffect.js'; import { useAsyncCallback, type AsyncCallbackState } from './useAsyncCallback.js'; +import { useInstantEffect } from './useInstantEffect.js'; /** * A hook that allows to execute an asynchronous function and provides the state of the execution. @@ -37,8 +37,8 @@ import { useAsyncCallback, type AsyncCallbackState } from './useAsyncCallback.js * } * ``` */ -export const useAsyncValue = , R>( - callback: ( ...args: A ) => Promise, +export const useAsyncValue = ( + callback: () => Promise, deps: DependencyList ): AsyncValueHookResult => { const [ asyncCallback, asyncState ] = useAsyncCallback( callback ); diff --git a/src/useMultiRootEditor.tsx b/src/useMultiRootEditor.tsx index 301550ae..ac16a3e6 100644 --- a/src/useMultiRootEditor.tsx +++ b/src/useMultiRootEditor.tsx @@ -396,6 +396,7 @@ const useMultiRootEditor = ( props: MultiRootHookProps ): MultiRootHookReturns = } ); setTimeout( () => { + /* istanbul ignore next -- @preserve */ if ( props.onReady ) { props.onReady( watchdog!.editor ); } diff --git a/tests/_utils/turnOffErrors.ts b/tests/_utils/turnOffErrors.ts new file mode 100644 index 00000000..340b683e --- /dev/null +++ b/tests/_utils/turnOffErrors.ts @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import type { Awaitable } from '@ckeditor/ckeditor5-integrations-common'; +import { timeout } from './timeout.js'; + +export async function turnOffErrors( callback: () => Awaitable ): Promise { + const handler = ( evt: ErrorEvent ) => { + evt.preventDefault(); + }; + + window.addEventListener( 'error', handler, { capture: true, once: true } ); + + try { + await callback(); + await timeout( 150 ); + } finally { + window.removeEventListener( 'error', handler ); + } +} diff --git a/tests/_utils/turnoffdefaulterrorcatching.js b/tests/_utils/turnoffdefaulterrorcatching.js deleted file mode 100644 index c2bfb30b..00000000 --- a/tests/_utils/turnoffdefaulterrorcatching.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* global window */ - -/** - * Turns off the default error catching - * so Mocha won't complain about errors caused by the called function. - */ -export default async function turnOffDefaultErrorCatching( fn ) { - const originalOnError = window.onerror; - window.onerror = () => {}; - - await fn(); - - window.onerror = originalOnError; -} diff --git a/tests/ckeditor.test.tsx b/tests/ckeditor.test.tsx index 81c862df..9e80f754 100644 --- a/tests/ckeditor.test.tsx +++ b/tests/ckeditor.test.tsx @@ -13,12 +13,12 @@ import MockedEditor from './_utils/editor.js'; import { timeout } from './_utils/timeout.js'; import { createDefer } from './_utils/defer.js'; import { PromiseManager } from './_utils/promisemanager.js'; -import turnOffDefaultErrorCatching from './_utils/turnoffdefaulterrorcatching.js'; import CKEditor, { type Props } from '../src/ckeditor.js'; import { expectToBeTruthy } from './_utils/expectToBeTruthy.js'; import type { LifeCycleElementSemaphore } from '../src/lifecycle/LifeCycleElementSemaphore.js'; import type { EditorSemaphoreMountResult } from '../src/lifecycle/LifeCycleEditorSemaphore.js'; +import { turnOffErrors } from './_utils/turnOffErrors.js'; const MockEditor = MockedEditor as any; @@ -1055,13 +1055,14 @@ describe( ' Component', () => { expect( firstEditor ).to.be.instanceOf( MockEditor ); - await turnOffDefaultErrorCatching( () => { - return new Promise( res => { + await manager.all(); + await turnOffErrors( async () => { + await new Promise( resolve => { component?.rerender( ); diff --git a/tests/cloud/useCKEditorCloud.test.tsx b/tests/cloud/useCKEditorCloud.test.tsx index bdd7ac12..3b71a0ce 100644 --- a/tests/cloud/useCKEditorCloud.test.tsx +++ b/tests/cloud/useCKEditorCloud.test.tsx @@ -4,16 +4,15 @@ */ import { afterEach, describe, expect, expectTypeOf, it } from 'vitest'; -import { renderHook, waitFor, act, cleanup } from '@testing-library/react'; +import { renderHook, waitFor, act } from '@testing-library/react'; import type { CKEditorCloudConfig } from '@ckeditor/ckeditor5-integrations-common'; import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils'; import useCKEditorCloud from '../../src/cloud/useCKEditorCloud.js'; -describe( 'useCKEditorCloud', () => { +describe( 'useCKEditorCloud', { timeout: 8000 }, () => { afterEach( () => { - cleanup(); removeAllCkCdnResources(); } ); @@ -31,7 +30,7 @@ describe( 'useCKEditorCloud', () => { if ( result.current.status === 'success' ) { expect( result.current.CKEditor ).toBeDefined(); } - } ); + }, { timeout: 5000 } ); } ); it( 'should load additional bundle after updating deps', async () => { @@ -52,7 +51,7 @@ describe( 'useCKEditorCloud', () => { expect( result.current.CKEditor ).toBeDefined(); expect( result.current.CKEditorPremiumFeatures ).toBeUndefined(); } - } ); + }, { timeout: 5000 } ); rerender( { version: '43.0.0', @@ -70,7 +69,7 @@ describe( 'useCKEditorCloud', () => { expect( result.current.CKEditor ).toBeDefined(); expect( result.current.CKEditorPremiumFeatures ).toBeDefined(); } - } ); + }, { timeout: 5000 } ); } ); describe( 'typings', () => { @@ -82,7 +81,7 @@ describe( 'useCKEditorCloud', () => { await waitFor( () => { expect( result.current.status ).toBe( 'success' ); - } ); + }, { timeout: 5000 } ); if ( result.current.status === 'success' ) { expectTypeOf( result.current.CKEditorPremiumFeatures ).not.toBeNullable(); @@ -97,7 +96,7 @@ describe( 'useCKEditorCloud', () => { await waitFor( () => { expect( result.current.status ).toBe( 'success' ); - } ); + }, { timeout: 5000 } ); if ( result.current.status === 'success' ) { expectTypeOf( result.current.CKEditorPremiumFeatures ).toBeNullable(); @@ -111,7 +110,7 @@ describe( 'useCKEditorCloud', () => { await waitFor( () => { expect( result.current.status ).toBe( 'success' ); - } ); + }, { timeout: 5000 } ); if ( result.current.status === 'success' ) { expectTypeOf( result.current.CKEditorPremiumFeatures ).toBeNullable(); @@ -128,7 +127,7 @@ describe( 'useCKEditorCloud', () => { await waitFor( () => { expect( result.current.status ).toBe( 'success' ); - } ); + }, { timeout: 5000 } ); if ( result.current.status === 'success' ) { expectTypeOf( result.current.CKBox ).not.toBeNullable(); @@ -142,7 +141,7 @@ describe( 'useCKEditorCloud', () => { await waitFor( () => { expect( result.current.status ).toBe( 'success' ); - } ); + }, { timeout: 5000 } ); if ( result.current.status === 'success' ) { expectTypeOf( result.current.CKBox ).toBeNullable(); diff --git a/tests/cloud/withCKEditorCloud.test.tsx b/tests/cloud/withCKEditorCloud.test.tsx index bb9d9923..2f5431ab 100644 --- a/tests/cloud/withCKEditorCloud.test.tsx +++ b/tests/cloud/withCKEditorCloud.test.tsx @@ -5,20 +5,19 @@ import React, { type MutableRefObject } from 'react'; import { afterEach, describe, expect, it } from 'vitest'; -import { cleanup, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { createDefer } from '@ckeditor/ckeditor5-integrations-common'; import { removeAllCkCdnResources } from '@ckeditor/ckeditor5-integrations-common/test-utils'; import withCKEditorCloud, { type WithCKEditorCloudHocProps } from '../../src/cloud/withCKEditorCloud.js'; -describe( 'withCKEditorCloud', () => { +describe( 'withCKEditorCloud', { timeout: 5000 }, () => { const lastRenderedMockProps: MutableRefObject = { current: null }; afterEach( () => { - cleanup(); removeAllCkCdnResources(); lastRenderedMockProps.current = null; } ); diff --git a/tests/context/ckeditorcontext.test.tsx b/tests/context/ckeditorcontext.test.tsx index 7338c52e..00fe6f5e 100644 --- a/tests/context/ckeditorcontext.test.tsx +++ b/tests/context/ckeditorcontext.test.tsx @@ -16,10 +16,10 @@ import CKEditorContext, { import CKEditor from '../../src/ckeditor.js'; import MockedEditor from '../_utils/editor.js'; import { ClassicEditor, ContextWatchdog, CKEditorError } from 'ckeditor5'; -import turnOffDefaultErrorCatching from '../_utils/turnoffdefaulterrorcatching.js'; import ContextMock, { DeferredContextMock } from '../_utils/context.js'; import { timeout } from '../_utils/timeout.js'; import { PromiseManager } from '../_utils/promisemanager.js'; +import { turnOffErrors } from '../_utils/turnOffErrors.js'; const MockEditor = MockedEditor as any; @@ -303,20 +303,19 @@ describe( ' Component', () => { } ); const { watchdog } = contextRef.current as ExtractContextWatchdogValueByStatus<'initialized'>; - const error = new CKEditorError( 'foo', watchdog.context ); - await turnOffDefaultErrorCatching( async () => { + await turnOffErrors( async () => { + const error = new CKEditorError( 'foo', watchdog.context ); + setTimeout( () => { throw error; } ); - - await timeout( 150 ); } ); expect( onErrorSpy ).toHaveBeenCalledOnce(); const errorEventArgs = onErrorSpy.mock.calls[ 0 ]; - expect( errorEventArgs[ 0 ] ).to.equal( error ); + expect( errorEventArgs[ 0 ] ).to.instanceOf( Error ); expect( errorEventArgs[ 1 ] ).to.deep.equal( { phase: 'runtime', willContextRestart: true diff --git a/tests/hooks/useAsyncCallback.test.tsx b/tests/hooks/useAsyncCallback.test.tsx index bd8ff77e..44678d5b 100644 --- a/tests/hooks/useAsyncCallback.test.tsx +++ b/tests/hooks/useAsyncCallback.test.tsx @@ -3,14 +3,12 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { renderHook, act, waitFor, cleanup } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { renderHook, act, waitFor } from '@testing-library/react'; import { useAsyncCallback } from '../../src/hooks/useAsyncCallback.js'; import { timeout } from '../_utils/timeout.js'; describe( 'useAsyncCallback', () => { - afterEach( cleanup ); - it( 'should execute the callback and update the state correctly when the callback resolves', async () => { const fetchData = vi.fn().mockResolvedValue( 'data' ); diff --git a/tests/hooks/useAsyncValue.test.tsx b/tests/hooks/useAsyncValue.test.tsx index fefeae62..f90af4a9 100644 --- a/tests/hooks/useAsyncValue.test.tsx +++ b/tests/hooks/useAsyncValue.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it } from 'vitest'; -import { cleanup, renderHook, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { renderHook, waitFor } from '@testing-library/react'; import { useAsyncValue } from '../../src/hooks/useAsyncValue.js'; describe( 'useAsyncValue', () => { - afterEach( cleanup ); - it( 'should return a mutable ref object', async () => { const { result } = renderHook( () => useAsyncValue( async () => 123, [] ) ); diff --git a/tests/hooks/useInstantEditorEffect.test.tsx b/tests/hooks/useInstantEditorEffect.test.tsx index f219e80b..b106b752 100644 --- a/tests/hooks/useInstantEditorEffect.test.tsx +++ b/tests/hooks/useInstantEditorEffect.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { cleanup, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; import { useInstantEditorEffect } from '../../src/hooks/useInstantEditorEffect.js'; describe( 'useInstantEditorEffect', () => { - afterEach( cleanup ); - it( 'should execute the provided function after mounting of editor', () => { const semaphore = { runAfterMount: vi.fn() diff --git a/tests/hooks/useInstantEffect.test.tsx b/tests/hooks/useInstantEffect.test.tsx index 8ca80565..f808a41b 100644 --- a/tests/hooks/useInstantEffect.test.tsx +++ b/tests/hooks/useInstantEffect.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { cleanup, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; import { useInstantEffect } from '../../src/hooks/useInstantEffect.js'; describe( 'useInstantEffect', () => { - afterEach( cleanup ); - it( 'should call the effect function when dependencies change', () => { const effectFn = vi.fn(); const { rerender } = renderHook( deps => useInstantEffect( effectFn, deps ), { diff --git a/tests/hooks/useIsMountedRef.test.tsx b/tests/hooks/useIsMountedRef.test.tsx index 03e8968a..87d8b1df 100644 --- a/tests/hooks/useIsMountedRef.test.tsx +++ b/tests/hooks/useIsMountedRef.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it } from 'vitest'; -import { cleanup, renderHook } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { renderHook } from '@testing-library/react'; import { useIsMountedRef } from '../../src/hooks/useIsMountedRef.js'; describe( 'useIsMountedRef', () => { - afterEach( cleanup ); - it( 'should return a mutable ref object', () => { const { result } = renderHook( () => useIsMountedRef() ); diff --git a/tests/hooks/useIsUnmountedRef.test.tsx b/tests/hooks/useIsUnmountedRef.test.tsx index 8369932c..087d98d7 100644 --- a/tests/hooks/useIsUnmountedRef.test.tsx +++ b/tests/hooks/useIsUnmountedRef.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { afterEach, describe, expect, it } from 'vitest'; -import { cleanup, renderHook } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { renderHook } from '@testing-library/react'; import { useIsUnmountedRef } from '../../src/hooks/useIsUnmountedRef.js'; describe( 'useIsUnmountedRef', () => { - afterEach( cleanup ); - it( 'should return a mutable ref object', () => { const { result } = renderHook( () => useIsUnmountedRef() ); diff --git a/tests/hooks/useRefSafeCallback.test.tsx b/tests/hooks/useRefSafeCallback.test.tsx index ee8a3226..3b8fa4a1 100644 --- a/tests/hooks/useRefSafeCallback.test.tsx +++ b/tests/hooks/useRefSafeCallback.test.tsx @@ -3,13 +3,11 @@ * For licensing, see LICENSE.md. */ -import { expect, it, describe, vi, afterEach } from 'vitest'; -import { renderHook, act, cleanup } from '@testing-library/react'; +import { expect, it, describe, vi } from 'vitest'; +import { renderHook, act } from '@testing-library/react'; import { useRefSafeCallback } from '../../src/hooks/useRefSafeCallback.js'; describe( 'useRefSafeCallback', () => { - afterEach( cleanup ); - it( 'should return a function', () => { const { result } = renderHook( () => useRefSafeCallback( () => {} ) ); diff --git a/tests/useMultiRootEditor.test.tsx b/tests/useMultiRootEditor.test.tsx index 8f82e0ca..1293a151 100644 --- a/tests/useMultiRootEditor.test.tsx +++ b/tests/useMultiRootEditor.test.tsx @@ -14,7 +14,7 @@ import { ContextWatchdogContext } from '../src/context/ckeditorcontext.js'; import { timeout } from './_utils/timeout.js'; import { createDefer } from './_utils/defer.js'; import { createTestMultiRootWatchdog, TestMultiRootEditor } from './_utils/multirooteditor.js'; -import turnOffDefaultErrorCatching from './_utils/turnoffdefaulterrorcatching.js'; +import { turnOffErrors } from './_utils/turnOffErrors.js'; describe( 'useMultiRootEditor', () => { const rootsContent = { @@ -120,17 +120,14 @@ describe( 'useMultiRootEditor', () => { // Mock the error. vi.spyOn( editor!, 'focus' ).mockImplementation( async () => { - await turnOffDefaultErrorCatching( () => { - return new Promise( () => { - setTimeout( () => { - throw new CKEditorError( 'a-custom-error', editor ); - } ); - } ); + setTimeout( () => { + throw new CKEditorError( 'a-custom-error', editor ); } ); } ); - // Throw the error. - editor!.focus(); + await turnOffErrors( async () => { + editor!.focus(); + } ); await waitFor( () => { const { editor: newEditor, data: newData, attributes: newAttributes } = result.current; @@ -208,18 +205,16 @@ describe( 'useMultiRootEditor', () => { const { editor, toolbarElement } = result.current; + // Mock the error. vi.spyOn( editor!, 'focus' ).mockImplementation( async () => { - await turnOffDefaultErrorCatching( () => { - return new Promise( () => { - setTimeout( () => { - throw new CKEditorError( 'a-custom-error', editor ); - } ); - } ); + setTimeout( () => { + throw new CKEditorError( 'a-custom-error', editor ); } ); } ); - // Throw the error. - editor!.focus(); + await turnOffErrors( async () => { + editor!.focus(); + } ); await waitFor( () => { const { toolbarElement: newToolbarElement } = result.current; @@ -603,8 +598,10 @@ describe( 'useMultiRootEditor', () => { const newRootsAttributes: Record = { ...rootsAttributes }; delete newRootsAttributes.intro; - act( () => { - setAttributes( { ...newRootsAttributes } ); + await turnOffErrors( () => { + act( () => { + setAttributes( { ...newRootsAttributes } ); + } ); } ); expect( console.error ).toHaveBeenCalledWith( '`data` and `attributes` objects must have the same keys (roots).' ); @@ -724,21 +721,18 @@ describe( 'useMultiRootEditor', () => { expect( result.current.editor ).to.be.instanceof( TestMultiRootEditor ); } ); - const { editor } = result.current; - // Mock the error. - vi.spyOn( editor!, 'focus' ).mockImplementation( async () => { - await turnOffDefaultErrorCatching( () => { - return new Promise( () => { - setTimeout( () => { - throw new CKEditorError( 'a-custom-error', editor ); - } ); + await turnOffErrors( async () => { + const { editor } = result.current; + + vi.spyOn( editor!, 'focus' ).mockImplementation( async () => { + setTimeout( () => { + throw new CKEditorError( 'a-custom-error', editor ); } ); } ); - } ); - // Throw the error. - editor!.focus(); + editor!.focus(); + } ); await waitFor( () => { expect( onAfterDestroyMock ).toHaveBeenCalledOnce(); diff --git a/vitest-setup.ts b/vitest-setup.ts index bbd99e5f..7643b274 100644 --- a/vitest-setup.ts +++ b/vitest-setup.ts @@ -4,3 +4,8 @@ */ import '@testing-library/jest-dom/vitest'; +import { cleanup } from '@testing-library/react'; +import { beforeEach, afterEach } from 'vitest'; + +beforeEach( cleanup ); +afterEach( cleanup );