1
1
/**
2
2
* Internal dependencies
3
3
*/
4
- import { kebabToCamelCase } from '../utils' ;
4
+ import { kebabToCamelCase , withScope } from '../utils' ;
5
+ import { setScope , getScope , resetScope , type Scope } from '../scopes' ;
6
+ import { setNamespace , getNamespace , resetNamespace } from '../namespaces' ;
5
7
6
8
describe ( 'Interactivity API' , ( ) => {
7
9
describe ( 'kebabToCamelCase' , ( ) => {
@@ -25,4 +27,209 @@ describe( 'Interactivity API', () => {
25
27
expect ( kebabToCamelCase ( '-my-item-' ) ) . toBe ( 'myItem' ) ;
26
28
} ) ;
27
29
} ) ;
30
+
31
+ describe ( 'withScope' , ( ) => {
32
+ const previousNamespace = 'previousNamespace' ;
33
+ const previousScope = {
34
+ evaluate : ( ) => { } ,
35
+ context : { } ,
36
+ serverContext : { } ,
37
+ ref : { current : null } ,
38
+ attributes : { } ,
39
+ } ;
40
+
41
+ const dummyNamespace = 'testNamespace' ;
42
+ const dummyScope = {
43
+ evaluate : ( ) => { } ,
44
+ context : { test : 'normal' } ,
45
+ serverContext : { test : 'normal' } ,
46
+ ref : { current : null } ,
47
+ attributes : { } ,
48
+ } ;
49
+
50
+ // Helper to mimic the wrapper used in state-proxy.ts and store-proxy.ts.
51
+ const dummyScopeAndNS = ( callback : ( ) => any ) => {
52
+ setScope ( dummyScope ) ;
53
+ setNamespace ( dummyNamespace ) ;
54
+ try {
55
+ return callback ( ) ;
56
+ } finally {
57
+ resetNamespace ( ) ;
58
+ resetScope ( ) ;
59
+ }
60
+ } ;
61
+
62
+ beforeEach ( ( ) => {
63
+ setScope ( previousScope ) ;
64
+ setNamespace ( previousNamespace ) ;
65
+ } ) ;
66
+
67
+ afterEach ( ( ) => {
68
+ resetNamespace ( ) ;
69
+ resetScope ( ) ;
70
+ } ) ;
71
+
72
+ it ( 'should return a function when passed a normal function' , ( ) => {
73
+ function normalFn ( x : number ) {
74
+ return x + 1 ;
75
+ }
76
+ const wrapped = withScope ( normalFn ) ;
77
+ expect ( typeof wrapped ) . toBe ( 'function' ) ;
78
+ } ) ;
79
+
80
+ it ( 'should call the original function, set the scope and namespace and restore them afterwards' , ( ) => {
81
+ let called = false ;
82
+ let scope : Scope ;
83
+ let namespace : string ;
84
+ function normalFn ( x : number ) {
85
+ called = true ;
86
+ scope = getScope ( ) ;
87
+ namespace = getNamespace ( ) ;
88
+ return x * 2 ;
89
+ }
90
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( normalFn ) ) ;
91
+ const result = wrapped ( 5 ) ;
92
+
93
+ expect ( result ) . toBe ( 10 ) ;
94
+ expect ( called ) . toBe ( true ) ;
95
+ expect ( scope ! ) . toBe ( dummyScope ) ;
96
+ expect ( namespace ! ) . toBe ( dummyNamespace ) ;
97
+ // After invocation, scope and namespace are reset.
98
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
99
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
100
+ } ) ;
101
+
102
+ it ( 'should return an async function when passed a generator function' , async ( ) => {
103
+ function * gen ( ) {
104
+ yield Promise . resolve ( 'value' ) ;
105
+ return 'done' ;
106
+ }
107
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
108
+ const resultPromise = wrapped ( ) ;
109
+
110
+ expect ( resultPromise ) . toBeInstanceOf ( Promise ) ;
111
+
112
+ const result = await resultPromise ;
113
+
114
+ expect ( result ) . toBe ( 'done' ) ;
115
+ // After invocation, scope and namespace are reset.
116
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
117
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
118
+ } ) ;
119
+
120
+ it ( 'should execute a generator function step by step and yield the correct values, maintaining scope and namespace' , async ( ) => {
121
+ const steps : Array < { scope : any ; namespace : string } > = [ ] ;
122
+ function * gen ( ) {
123
+ steps . push ( { scope : getScope ( ) , namespace : getNamespace ( ) } ) ;
124
+ const a = yield Promise . resolve ( 1 ) ;
125
+ steps . push ( { scope : getScope ( ) , namespace : getNamespace ( ) } ) ;
126
+ const b = yield Promise . resolve ( a + 1 ) ;
127
+ steps . push ( { scope : getScope ( ) , namespace : getNamespace ( ) } ) ;
128
+ return b * 2 ;
129
+ }
130
+ const callback = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
131
+ const result = await callback ( ) ;
132
+
133
+ expect ( result ) . toBe ( 4 ) ;
134
+ steps . forEach ( ( step ) => {
135
+ expect ( step . scope ) . toBe ( dummyScope ) ;
136
+ expect ( step . namespace ) . toBe ( dummyNamespace ) ;
137
+ } ) ;
138
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
139
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
140
+ } ) ;
141
+
142
+ it ( 'should return the resolved value when a promise is returned in generator functions' , async ( ) => {
143
+ function * gen ( ) {
144
+ const a = yield Promise . resolve ( 3 ) ;
145
+ return Promise . resolve ( a + 2 ) ;
146
+ }
147
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
148
+ const result = await wrapped ( ) ;
149
+ expect ( result ) . toBe ( 5 ) ;
150
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
151
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
152
+ } ) ;
153
+
154
+ it ( 'should accept arguments in generator functions like in normal functions' , async ( ) => {
155
+ function * gen ( ...values : number [ ] ) {
156
+ let result = 0 ;
157
+ for ( const value of values ) {
158
+ result += yield Promise . resolve ( value ) ;
159
+ }
160
+ return result ;
161
+ }
162
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
163
+ const result = await wrapped ( 1 , 2 , 3 , 4 ) ;
164
+ expect ( result ) . toBe ( 10 ) ;
165
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
166
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
167
+ } ) ;
168
+
169
+ it ( 'should propagate errors thrown inside a generator function' , async ( ) => {
170
+ function * gen ( ) {
171
+ throw new Error ( 'Test Error' ) ;
172
+ }
173
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
174
+ await expect ( wrapped ( ) ) . rejects . toThrow ( 'Test Error' ) ;
175
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
176
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
177
+ } ) ;
178
+
179
+ it ( 'should propagate errors when a generator function returns a rejected promise' , async ( ) => {
180
+ function * gen ( ) {
181
+ return Promise . reject ( new Error ( 'Test Error' ) ) ;
182
+ }
183
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
184
+ await expect ( wrapped ( ) ) . rejects . toThrow ( 'Test Error' ) ;
185
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
186
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
187
+ } ) ;
188
+
189
+ it ( 'hould handle captured errors within generator execution and resume correctly, maintaining scope and namespace' , async ( ) => {
190
+ const steps : Array < { scope : any ; namespace : string } > = [ ] ;
191
+ function * gen ( ) {
192
+ let a : number ;
193
+ try {
194
+ steps . push ( {
195
+ scope : getScope ( ) ,
196
+ namespace : getNamespace ( ) ,
197
+ } ) ;
198
+ a = yield Promise . reject ( new Error ( 'CatchMe' ) ) ;
199
+ } catch ( e ) {
200
+ steps . push ( {
201
+ scope : getScope ( ) ,
202
+ namespace : getNamespace ( ) ,
203
+ } ) ;
204
+ a = 10 ;
205
+ }
206
+ steps . push ( { scope : getScope ( ) , namespace : getNamespace ( ) } ) ;
207
+ const b = yield Promise . resolve ( a + 5 ) ;
208
+ steps . push ( { scope : getScope ( ) , namespace : getNamespace ( ) } ) ;
209
+ return b ;
210
+ }
211
+ const callback = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
212
+ const result = await callback ( ) ;
213
+
214
+ expect ( result ) . toBe ( 15 ) ;
215
+ steps . forEach ( ( step ) => {
216
+ expect ( step . scope ) . toBe ( dummyScope ) ;
217
+ expect ( step . namespace ) . toBe ( dummyNamespace ) ;
218
+ } ) ;
219
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
220
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
221
+ } ) ;
222
+
223
+ it ( 'should handle rejected promises within a generator function and throw after yielding' , async ( ) => {
224
+ function * gen ( ) {
225
+ const a = yield Promise . resolve ( 2 ) ;
226
+ yield Promise . reject ( new Error ( 'FinalReject' ) ) ;
227
+ return a ;
228
+ }
229
+ const wrapped = dummyScopeAndNS ( ( ) => withScope ( gen ) ) ;
230
+ await expect ( wrapped ( ) ) . rejects . toThrow ( 'FinalReject' ) ;
231
+ expect ( getScope ( ) ) . toBe ( previousScope ) ;
232
+ expect ( getNamespace ( ) ) . toBe ( previousNamespace ) ;
233
+ } ) ;
234
+ } ) ;
28
235
} ) ;
0 commit comments