// @vitest-environment jsdom import { FunctionalComponent as FC, h } from 'preact'; import { render, cleanup, act } from '@testing-library/preact'; import { OperationContext } from '@urql/core'; import { vi, expect, it, beforeEach, describe, beforeAll, Mock, afterEach, } from 'vitest'; import { merge, fromValue, never, empty } from 'wonka'; import { useSubscription, UseSubscriptionState } from './useSubscription'; import { Provider } from '../context'; const data = { data: 1234, error: 5678 }; const mock = { // @ts-ignore executeSubscription: vi.fn(() => merge([fromValue(data), never])), }; const client = mock as { executeSubscription: Mock }; const query = 'subscription Example { example }'; let state: UseSubscriptionState | undefined; let execute: ((_opts?: Partial) => void) | undefined; const SubscriptionUser: FC<{ q: string; handler?: (_prev: any, _data: any) => any; context?: Partial; pause?: boolean; }> = ({ q, handler, context, pause = false }) => { [state, execute] = useSubscription({ query: q, context, pause }, handler); return h('p', {}, state.data); }; beforeAll(() => { vi.spyOn(globalThis.console, 'error').mockImplementation(() => { // do nothing }); }); describe('useSubscription', () => { beforeEach(() => { client.executeSubscription.mockClear(); state = undefined; execute = undefined; }); const props = { q: query }; afterEach(() => cleanup()); it('executes subscription', () => { render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); expect(client.executeSubscription).toBeCalledTimes(1); }); it('should support setting context in useSubscription params', () => { render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props, context: { url: 'test' } })], }) ); expect(client.executeSubscription).toBeCalledWith( { key: expect.any(Number), query: expect.any(Object), variables: {}, }, { url: 'test', } ); }); describe('on subscription', () => { it('forwards client response', () => { const { rerender } = render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); /** * Have to call update (without changes) in order to see the * result of the state change. */ rerender( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); expect(state).toEqual({ ...data, hasNext: false, extensions: undefined, fetching: true, stale: false, }); }); }); it('calls handler', () => { const handler = vi.fn(); const { rerender } = render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props, handler })], }) ); rerender( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); expect(handler).toBeCalledTimes(2); expect(handler).toBeCalledWith(undefined, 1234); }); describe('active teardown', () => { it('sets fetching to false when the source ends', () => { client.executeSubscription.mockReturnValueOnce(empty); render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); expect(client.executeSubscription).toHaveBeenCalled(); expect(state).toMatchObject({ fetching: false }); }); }); describe('execute subscription', () => { it('triggers subscription execution', () => { render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); act(() => execute && execute()); expect(client.executeSubscription).toBeCalledTimes(2); }); }); describe('pause', () => { const props = { q: query }; it('skips executing the query if pause is true', () => { render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props, pause: true })], }) ); expect(client.executeSubscription).not.toBeCalled(); }); it('skips executing queries if pause updates to true', () => { const { rerender } = render( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props })], }) ); rerender( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props, pause: true })], }) ); rerender( h(Provider, { value: client as any, children: [h(SubscriptionUser, { ...props, pause: true })], }) ); expect(client.executeSubscription).toBeCalledTimes(1); expect(state).toMatchObject({ fetching: false }); }); }); });