11import type { Future , Operation , Result , Scope } from "effection" ;
2- import { createScope , Err , Ok } from "effection" ;
2+ import { Err , Ok , run , suspend , useScope , withResolvers } from "effection" ;
33
44export interface TestOperation {
55 ( ) : Operation < void > ;
@@ -23,12 +23,6 @@ export interface TestAdapter {
2323 */
2424 readonly fullname : string ;
2525
26- /**
27- * Every test adapter has its own Effection `Scope` which holds the resources necessary
28- * to run this test.
29- */
30- readonly scope : Scope ;
31-
3226 /**
3327 * A list of this test adapter and every adapter that it descends from.
3428 */
@@ -38,14 +32,20 @@ export interface TestAdapter {
3832 * The setup operations that will be run by this test adapter. It only includes those
3933 * setups that are associated with this adapter, not those of its ancestors.
4034 */
41- readonly setups : TestOperation [ ] ;
35+ readonly setup : { all : TestOperation [ ] ; each : TestOperation [ ] } ;
4236
4337 /**
4438 * Add a setup operation to every test that is part of this adapter. In BDD integrations,
4539 * this is usually called by `beforEach()`
4640 */
4741 addSetup ( op : TestOperation ) : void ;
4842
43+ /**
44+ * Add a setup operation that will run exactly once before any tests that are run in this
45+ * adapter. In BDD integrations, this is usually called by beforeAll()
46+ */
47+ addOnetimeSetup ( op : TestOperation ) : void ;
48+
4949 /**
5050 * Actually run a test. This evaluates all setup operations, and then after those have completed
5151 * it runs the body of the test itself.
@@ -57,6 +57,13 @@ export interface TestAdapter {
5757 * This basically destroys the Effection `Scope` associated with this adapter.
5858 */
5959 destroy ( ) : Future < void > ;
60+
61+ /**
62+ * Used internally to prepare adapters to run test
63+ *
64+ * @ignore
65+ */
66+ [ "@@init@@" ] ( ) : Operation < Scope > ;
6067}
6168
6269export interface TestAdapterOptions {
@@ -87,16 +94,18 @@ const anonymousNames: Iterator<string, never> = (function* () {
8794export function createTestAdapter (
8895 options : TestAdapterOptions = { } ,
8996) : TestAdapter {
90- const setups : TestOperation [ ] = [ ] ;
97+ const setup = {
98+ all : [ ] as TestOperation [ ] ,
99+ each : [ ] as TestOperation [ ] ,
100+ } ;
91101 const { parent, name = anonymousNames . next ( ) . value } = options ;
92102
93- const [ scope , destroy ] = createScope ( parent ?. scope ) ;
103+ let scope : Scope | undefined = undefined ;
94104
95105 const adapter : TestAdapter = {
96106 parent,
97107 name,
98- scope,
99- setups,
108+ setup,
100109 get lineage ( ) {
101110 const lineage = [ adapter ] ;
102111 for ( let current = parent ; current ; current = current . parent ) {
@@ -108,27 +117,69 @@ export function createTestAdapter(
108117 return adapter . lineage . map ( ( adapter ) => adapter . name ) . join ( " > " ) ;
109118 } ,
110119 addSetup ( op ) {
111- setups . push ( op ) ;
120+ setup . each . push ( op ) ;
121+ } ,
122+ addOnetimeSetup ( op ) {
123+ setup . all . push ( op ) ;
112124 } ,
113125 runTest ( op ) {
114- return scope . run ( function * ( ) {
115- const allSetups = adapter . lineage . reduce (
116- ( all , adapter ) => all . concat ( adapter . setups ) ,
117- [ ] as TestOperation [ ] ,
118- ) ;
119- try {
120- for ( const setup of allSetups ) {
121- yield * setup ( ) ;
122- }
126+ return run ( ( ) =>
127+ box ( function * ( ) {
128+ const setups = adapter . lineage . reduce (
129+ ( all , adapter ) => all . concat ( adapter . setup . each ) ,
130+ [ ] as TestOperation [ ] ,
131+ ) ;
132+
133+ let scope = yield * adapter [ "@@init@@" ] ( ) ;
134+
135+ let test = yield * scope . spawn ( function * ( ) {
136+ for ( const setup of setups ) {
137+ yield * setup ( ) ;
138+ }
139+ yield * op ( ) ;
140+ } ) ;
141+
142+ yield * test ;
143+ } ( ) )
144+ ) ;
145+ } ,
146+
147+ // no-op that will be replaced once initialze
148+ destroy : ( ) => run ( function * ( ) { } ) ,
149+
150+ * [ "@@init@@" ] ( ) {
151+ if ( scope ) {
152+ return scope ;
153+ }
154+
155+ let parentScope = parent
156+ ? yield * parent [ "@@init@@" ] ( )
157+ : yield * useScope ( ) ;
158+
159+ let initialized = withResolvers < Scope > ( ) ;
160+
161+ let task = yield * parentScope . spawn ( function * ( ) {
162+ scope = yield * useScope ( ) ;
163+ for ( let op of setup . all ) {
123164 yield * op ( ) ;
124- return Ok < void > ( void 0 ) ;
125- } catch ( error ) {
126- return Err ( error as Error ) ;
127165 }
166+ initialized . resolve ( scope ) ;
167+ yield * suspend ( ) ;
128168 } ) ;
169+
170+ adapter . destroy = ( ) => run ( task . halt ) ;
171+
172+ return yield * initialized . operation ;
129173 } ,
130- destroy,
131174 } ;
132175
133176 return adapter ;
134177}
178+
179+ function * box < T > ( op : Operation < T > ) : Operation < Result < T > > {
180+ try {
181+ return Ok ( yield * op ) ;
182+ } catch ( error ) {
183+ return Err ( error as Error ) ;
184+ }
185+ }
0 commit comments