Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 5.2 kB view raw
1// @vitest-environment jsdom 2 3import { FunctionalComponent as FC, h } from 'preact'; 4import { render, cleanup, act } from '@testing-library/preact'; 5import { OperationContext } from '@urql/core'; 6import { 7 vi, 8 expect, 9 it, 10 beforeEach, 11 describe, 12 beforeAll, 13 Mock, 14 afterEach, 15} from 'vitest'; 16import { merge, fromValue, never, empty } from 'wonka'; 17 18import { useSubscription, UseSubscriptionState } from './useSubscription'; 19import { Provider } from '../context'; 20 21const data = { data: 1234, error: 5678 }; 22const mock = { 23 // @ts-ignore 24 executeSubscription: vi.fn(() => merge([fromValue(data), never])), 25}; 26 27const client = mock as { executeSubscription: Mock }; 28const query = 'subscription Example { example }'; 29 30let state: UseSubscriptionState<any> | undefined; 31let execute: ((_opts?: Partial<OperationContext>) => void) | undefined; 32 33const SubscriptionUser: FC<{ 34 q: string; 35 handler?: (_prev: any, _data: any) => any; 36 context?: Partial<OperationContext>; 37 pause?: boolean; 38}> = ({ q, handler, context, pause = false }) => { 39 [state, execute] = useSubscription({ query: q, context, pause }, handler); 40 return h('p', {}, state.data); 41}; 42 43beforeAll(() => { 44 vi.spyOn(globalThis.console, 'error').mockImplementation(() => { 45 // do nothing 46 }); 47}); 48 49describe('useSubscription', () => { 50 beforeEach(() => { 51 client.executeSubscription.mockClear(); 52 state = undefined; 53 execute = undefined; 54 }); 55 56 const props = { q: query }; 57 58 afterEach(() => cleanup()); 59 60 it('executes subscription', () => { 61 render( 62 h(Provider, { 63 value: client as any, 64 children: [h(SubscriptionUser, { ...props })], 65 }) 66 ); 67 expect(client.executeSubscription).toBeCalledTimes(1); 68 }); 69 70 it('should support setting context in useSubscription params', () => { 71 render( 72 h(Provider, { 73 value: client as any, 74 children: [h(SubscriptionUser, { ...props, context: { url: 'test' } })], 75 }) 76 ); 77 expect(client.executeSubscription).toBeCalledWith( 78 { 79 key: expect.any(Number), 80 query: expect.any(Object), 81 variables: {}, 82 }, 83 { 84 url: 'test', 85 } 86 ); 87 }); 88 89 describe('on subscription', () => { 90 it('forwards client response', () => { 91 const { rerender } = render( 92 h(Provider, { 93 value: client as any, 94 children: [h(SubscriptionUser, { ...props })], 95 }) 96 ); 97 /** 98 * Have to call update (without changes) in order to see the 99 * result of the state change. 100 */ 101 rerender( 102 h(Provider, { 103 value: client as any, 104 children: [h(SubscriptionUser, { ...props })], 105 }) 106 ); 107 expect(state).toEqual({ 108 ...data, 109 hasNext: false, 110 extensions: undefined, 111 fetching: true, 112 stale: false, 113 }); 114 }); 115 }); 116 117 it('calls handler', () => { 118 const handler = vi.fn(); 119 const { rerender } = render( 120 h(Provider, { 121 value: client as any, 122 children: [h(SubscriptionUser, { ...props, handler })], 123 }) 124 ); 125 rerender( 126 h(Provider, { 127 value: client as any, 128 children: [h(SubscriptionUser, { ...props })], 129 }) 130 ); 131 expect(handler).toBeCalledTimes(2); 132 expect(handler).toBeCalledWith(undefined, 1234); 133 }); 134 135 describe('active teardown', () => { 136 it('sets fetching to false when the source ends', () => { 137 client.executeSubscription.mockReturnValueOnce(empty); 138 render( 139 h(Provider, { 140 value: client as any, 141 children: [h(SubscriptionUser, { ...props })], 142 }) 143 ); 144 expect(client.executeSubscription).toHaveBeenCalled(); 145 expect(state).toMatchObject({ fetching: false }); 146 }); 147 }); 148 149 describe('execute subscription', () => { 150 it('triggers subscription execution', () => { 151 render( 152 h(Provider, { 153 value: client as any, 154 children: [h(SubscriptionUser, { ...props })], 155 }) 156 ); 157 act(() => execute && execute()); 158 expect(client.executeSubscription).toBeCalledTimes(2); 159 }); 160 }); 161 162 describe('pause', () => { 163 const props = { q: query }; 164 165 it('skips executing the query if pause is true', () => { 166 render( 167 h(Provider, { 168 value: client as any, 169 children: [h(SubscriptionUser, { ...props, pause: true })], 170 }) 171 ); 172 expect(client.executeSubscription).not.toBeCalled(); 173 }); 174 175 it('skips executing queries if pause updates to true', () => { 176 const { rerender } = render( 177 h(Provider, { 178 value: client as any, 179 children: [h(SubscriptionUser, { ...props })], 180 }) 181 ); 182 183 rerender( 184 h(Provider, { 185 value: client as any, 186 children: [h(SubscriptionUser, { ...props, pause: true })], 187 }) 188 ); 189 rerender( 190 h(Provider, { 191 value: client as any, 192 children: [h(SubscriptionUser, { ...props, pause: true })], 193 }) 194 ); 195 expect(client.executeSubscription).toBeCalledTimes(1); 196 expect(state).toMatchObject({ fetching: false }); 197 }); 198 }); 199});