UNPKG

@wordpress/interactivity

Version:

Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.

374 lines (336 loc) 9.53 kB
/** * Internal dependencies */ import { store, type AsyncAction, type TypeYield } from '../store'; describe( 'Interactivity API', () => { describe( 'store', () => { it( 'dummy test', () => { expect( true ).toBe( true ); } ); describe( 'types', () => { describe( 'the whole store can be inferred', () => { // eslint-disable-next-line no-unused-expressions async () => { const myStore = store( 'test', { state: { clientValue: 1, get derived(): number { return myStore.state.clientValue; }, }, actions: { sync( n: number ) { return n; }, *async( n: number ) { const n1: number = yield myStore.actions.sync( n ); return myStore.state.derived + n1 + n; }, }, } ); myStore.state.clientValue satisfies number; myStore.state.derived satisfies number; // @ts-expect-error myStore.state.nonExistent satisfies number; myStore.actions.sync( 1 ) satisfies number; myStore.actions.async( 1 ) satisfies Promise< number >; ( await myStore.actions.async( 1 ) ) satisfies number; // @ts-expect-error myStore.actions.nonExistent() satisfies {}; }; } ); describe( 'the whole store can be manually typed', () => { // eslint-disable-next-line no-unused-expressions async () => { interface Store { state: { clientValue: number; serverValue: number; readonly derived: number; }; actions: { sync: ( n: number ) => number; async: ( n: number ) => Promise< number >; }; } const myStore = store< Store >( 'test', { state: { clientValue: 1, // @ts-expect-error nonExistent: 2, get derived(): number { return myStore.state.serverValue; }, }, actions: { sync( n ) { return n; }, *async( n ): AsyncAction< number > { const n1 = myStore.actions.sync( n ); return myStore.state.derived + n1 + n; }, }, } ); myStore.state.clientValue satisfies number; myStore.state.serverValue satisfies number; myStore.state.derived satisfies number; // @ts-expect-error myStore.state.nonExistent satisfies number; myStore.actions.sync( 1 ) satisfies number; myStore.actions.async( 1 ) satisfies Promise< number >; ( await myStore.actions.async( 1 ) ) satisfies number; // @ts-expect-error myStore.actions.nonExistent(); }; } ); describe( 'the server state can be typed and the rest inferred', () => { // eslint-disable-next-line no-unused-expressions async () => { type ServerStore = { state: { serverValue: number; }; }; const clientStore = { state: { clientValue: 1, get derived(): number { return myStore.state.serverValue; }, }, actions: { sync( n: number ) { return n; }, *async( n: number ): AsyncAction< number > { const n1 = ( yield myStore.actions.async2( n ) ) as TypeYield< typeof myStore.actions.async2 > satisfies number; return myStore.state.derived + n1 + n; }, *async2( n: number ) { return n; }, }, }; type Store = ServerStore & typeof clientStore; const myStore = store< Store >( 'test', clientStore ); myStore.state.clientValue satisfies number; myStore.state.serverValue satisfies number; myStore.state.derived satisfies number; // @ts-expect-error myStore.state.nonExistent satisfies number; myStore.actions.sync( 1 ) satisfies number; myStore.actions.async( 1 ) satisfies Promise< number >; ( await myStore.actions.async( 1 ) ) satisfies number; // @ts-expect-error myStore.actions.nonExistent(); }; } ); describe( 'the state can be casted and the rest inferred', () => { // eslint-disable-next-line no-unused-expressions async () => { type State = { clientValue: number; serverValue: number; derived: number; }; const myStore = store( 'test', { state: { clientValue: 1, get derived(): number { return myStore.state.serverValue; }, } as State, actions: { sync( n: number ) { return n; }, *async( n: number ): AsyncAction< number > { const n1 = ( yield myStore.actions.async2( n ) ) as TypeYield< typeof myStore.actions.async2 > satisfies number; return myStore.state.derived + n1 + n; }, *async2( n: number ) { return n; }, }, } ); myStore.state.clientValue satisfies number; myStore.state.serverValue satisfies number; myStore.state.derived satisfies number; // @ts-expect-error myStore.state.nonExistent satisfies number; myStore.actions.sync( 1 ) satisfies number; myStore.actions.async( 1 ) satisfies Promise< number >; ( await myStore.actions.async( 1 ) ) satisfies number; // @ts-expect-error myStore.actions.nonExistent() satisfies {}; }; } ); describe( 'the whole store can be manually typed even if doesnt contain state', () => { // eslint-disable-next-line no-unused-expressions async () => { interface Store { actions: { sync: ( n: number ) => number; async: ( n: number ) => Promise< number >; async2: ( n: number ) => AsyncAction< number >; }; callbacks: { existent: number; }; } const myStore = store< Store >( 'test', { actions: { sync( n ) { return n; }, *async( n ): AsyncAction< number > { const n1 = ( yield myStore.actions.async2( n ) ) as TypeYield< typeof myStore.actions.async2 > satisfies number; return n1 + n; }, *async2( n: number ) { return n; }, }, callbacks: { existent: 1, // @ts-expect-error nonExistent: 1, }, } ); // @ts-expect-error myStore.state.nonExistent satisfies number; myStore.actions.sync( 1 ) satisfies number; myStore.actions.async( 1 ) satisfies Promise< number >; ( await myStore.actions.async( 1 ) ) satisfies number; myStore.callbacks.existent satisfies number; // @ts-expect-error myStore.callbacks.nonExistent satisfies number; // @ts-expect-error myStore.actions.nonExistent() satisfies {}; }; } ); describe( 'the store can be divided into multiple parts', () => { // eslint-disable-next-line no-unused-expressions async () => { type ServerState = { state: { serverValue: number; }; }; const firstStorePart = { state: { clientValue1: 1, }, actions: { incrementValue1( n = 1 ) { myStore.state.clientValue1 += n; }, }, }; type FirstStorePart = typeof firstStorePart; const secondStorePart = { state: { clientValue2: 'test', }, actions: { *asyncAction() { return ( myStore.state.clientValue1 + myStore.state.serverValue ); }, }, }; type Store = ServerState & FirstStorePart & typeof secondStorePart; const myStore = store< Store >( 'test', firstStorePart ); store( 'test', secondStorePart ); myStore.state.clientValue1 satisfies number; myStore.state.clientValue2 satisfies string; myStore.actions.incrementValue1( 1 ); myStore.actions.asyncAction() satisfies Promise< number >; ( await myStore.actions.asyncAction() ) satisfies number; // @ts-expect-error myStore.state.nonExistent satisfies {}; }; } ); describe( 'a typed store can be returned without adding a new store part', () => { type State = { someValue: number; }; type Actions = { incrementValue: ( n: number ) => void; }; const { state, actions } = store< { state: State; actions: Actions; } >( 'storeWithState', { actions: { incrementValue( n ) { state.someValue += n; }, }, } ); state.someValue satisfies number; actions.incrementValue( 1 ) satisfies void; const { actions: actions2 } = store< { actions: Actions } >( 'storeWithoutState', { actions: { incrementValue( n ) { state.someValue += n; }, }, } ); actions2.incrementValue( 1 ) satisfies void; } ); describe( 'async actions can pass state to yields and type the yield returns', () => { // eslint-disable-next-line no-unused-expressions async () => { type Store = { state: { someValue: string; }; actions: { asyncAction: () => Promise< number >; }; }; const asyncFunction = async ( someValue: string ): Promise< string > => { return someValue; }; const { state, actions } = store< Store >( 'test', { actions: { *asyncAction(): AsyncAction< number > { ( yield asyncFunction( state.someValue ) ) as TypeYield< typeof asyncFunction > satisfies string; return 1; }, }, } ); ( await actions.asyncAction() ) satisfies number; }; } ); } ); } ); } );