2021-09-13 18:33:13 +02:00
/ *
This file is part of GNU Taler
( C ) 2021 Taler Systems S . A .
GNU Taler is free software ; you can redistribute it and / or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation ; either version 3 , or ( at your option ) any later version .
GNU Taler is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License along with
GNU Taler ; see the file COPYING . If not , see < http : / / www.gnu.org / licenses / >
* /
2022-03-24 20:02:38 +01:00
import { ComponentChildren , Fragment , FunctionalComponent , h as create , options , render as renderIntoDom , VNode } from "preact" ;
2022-01-04 21:06:17 +01:00
import { render as renderToString } from "preact-render-to-string" ;
2021-08-19 05:34:47 +02:00
2022-03-24 20:02:38 +01:00
// When doing tests we want the requestAnimationFrame to be as fast as possible.
// without this option the RAF will timeout after 100ms making the tests slower
options . requestAnimationFrame = ( fn : ( ) = > void ) = > {
// console.log("RAF called")
return fn ( )
}
2021-11-15 15:18:58 +01:00
export function createExample < Props > (
Component : FunctionalComponent < Props > ,
props : Partial < Props > ,
2021-12-06 14:31:19 +01:00
) : ComponentChildren {
2022-01-04 21:06:17 +01:00
const Render = ( args : any ) : VNode = > create ( Component , args ) ;
2021-12-06 14:31:19 +01:00
Render . args = props ;
return Render ;
}
export function createExampleWithCustomContext < Props , ContextProps > (
Component : FunctionalComponent < Props > ,
props : Partial < Props > ,
ContextProvider : FunctionalComponent < ContextProps > ,
contextProps : Partial < ContextProps > ,
) : ComponentChildren {
2022-01-04 21:06:17 +01:00
const Render = ( args : any ) : VNode = > create ( Component , args ) ;
const WithContext = ( args : any ) : VNode = > create ( ContextProvider , { . . . contextProps , children : [ Render ( args ) ] } as any ) ;
2021-12-06 14:31:19 +01:00
WithContext . args = props
return WithContext
2021-08-19 05:34:47 +02:00
}
2022-01-04 21:06:17 +01:00
export function NullLink ( { children } : { children? : ComponentChildren } ) : VNode {
return create ( "a" , { children , href : "javascript:void(0);" } ) ;
2021-08-23 21:51:49 +02:00
}
2022-01-04 21:06:17 +01:00
export function renderNodeOrBrowser ( Component : any , args : any ) : void {
const vdom = create ( Component , args ) ;
if ( typeof window === "undefined" ) {
renderToString ( vdom ) ;
} else {
const div = document . createElement ( "div" ) ;
document . body . appendChild ( div ) ;
renderIntoDom ( vdom , div ) ;
renderIntoDom ( null , div ) ;
document . body . removeChild ( div ) ;
}
}
interface Mounted < T > {
unmount : ( ) = > void ;
2022-04-11 16:36:32 +02:00
getLastResult : ( ) = > T | null ;
getLastResultOrThrow : ( ) = > T ;
assertNoPendingUpdate : ( ) = > void ;
2022-03-24 20:02:38 +01:00
waitNextUpdate : ( s? : string ) = > Promise < void > ;
2022-01-04 21:06:17 +01:00
}
2022-03-23 20:20:39 +01:00
const isNode = typeof window === "undefined"
export function mountHook < T > ( callback : ( ) = > T , Context ? : ( { children } : { children : any } ) = > VNode ) : Mounted < T > {
2022-04-11 16:36:32 +02:00
// const result: { current: T | null } = {
// current: null
// }
let lastResult : T | null = null ;
2022-01-04 21:06:17 +01:00
const listener : Array < ( ) = > void > = [ ]
// component that's going to hold the hook
function Component ( ) : VNode {
const hookResult = callback ( )
// save the hook result
2022-04-11 16:36:32 +02:00
lastResult = hookResult
2022-01-04 21:06:17 +01:00
// notify to everyone waiting for an update and clean the queue
listener . splice ( 0 , listener . length ) . forEach ( cb = > cb ( ) )
return create ( Fragment , { } )
}
// create the vdom with context if required
const vdom = ! Context ? create ( Component , { } ) : create ( Context , { children : [ create ( Component , { } ) ] } , ) ;
// waiter callback
2022-03-24 20:02:38 +01:00
async function waitNextUpdate ( _label = "" ) : Promise < void > {
if ( _label ) _label = ` . label: " ${ _label } " `
2022-01-04 21:06:17 +01:00
await new Promise ( ( res , rej ) = > {
const tid = setTimeout ( ( ) = > {
2022-03-24 20:02:38 +01:00
rej ( Error ( ` waiting for an update but the hook didn't make one ${ _label } ` ) )
2022-01-04 21:06:17 +01:00
} , 100 )
listener . push ( ( ) = > {
clearTimeout ( tid )
res ( undefined )
} )
} )
}
2022-03-23 20:20:39 +01:00
const customElement = { } as Element
const parentElement = isNode ? customElement : document.createElement ( "div" ) ;
if ( ! isNode ) {
document . body . appendChild ( parentElement ) ;
}
renderIntoDom ( vdom , parentElement ) ;
// clean up callback
2022-03-29 14:58:06 +02:00
function unmount ( ) : void {
2022-03-23 20:20:39 +01:00
if ( ! isNode ) {
document . body . removeChild ( parentElement ) ;
}
}
2022-04-11 16:36:32 +02:00
function getLastResult ( ) : T | null {
const copy = lastResult
lastResult = null
return copy ;
}
function getLastResultOrThrow ( ) : T {
const r = getLastResult ( )
if ( ! r ) throw Error ( 'there was no last result' )
return r ;
}
async function assertNoPendingUpdate ( ) : Promise < void > {
await new Promise ( ( res , rej ) = > {
const tid = setTimeout ( ( ) = > {
res ( undefined )
} , 10 )
listener . push ( ( ) = > {
clearTimeout ( tid )
rej ( Error ( ` Expecting no pending result but the hook get updated. Check the dependencies of the hooks. ` ) )
} )
} )
const r = getLastResult ( )
if ( r ) throw Error ( 'There are still pending results. This may happen because the hook did a new update but the test didn\'t get the result using getLastResult' ) ;
}
2022-01-04 21:06:17 +01:00
return {
2022-04-11 16:36:32 +02:00
unmount , getLastResult , getLastResultOrThrow , waitNextUpdate , assertNoPendingUpdate
2022-01-04 21:06:17 +01:00
}
}