@@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
22import os from 'node:os'
33import path from 'node:path'
44
5- import { afterEach , expect , test , vi } from 'vitest'
5+ import { expect , test , vi } from 'vitest'
66
77vi . mock ( '@epic-web/workshop-utils/compile-mdx.server' , async ( ) => {
88 const fs = await import ( 'node:fs/promises' )
@@ -84,56 +84,49 @@ async function createWorkshopFixture({
8484 `# Step Solution\n\n<EpicVideo url="https://${ productHost } /workshops/${ productSlug } /step-solution" />\n` ,
8585 )
8686
87- return root
87+ return {
88+ root,
89+ async [ Symbol . asyncDispose ] ( ) {
90+ await fs . rm ( root , { recursive : true , force : true } )
91+ } ,
92+ }
8893}
8994
90- afterEach ( async ( ) => {
91- vi . unstubAllGlobals ( )
92- } )
93-
9495test ( 'passes with configured product + videos (skip remote)' , async ( ) => {
95- const workshopRoot = await createWorkshopFixture ( )
96-
97- try {
98- await expect (
99- launchReadiness ( {
100- workshopRoot,
101- silent : true ,
102- skipRemote : true ,
103- skipHead : true ,
104- } ) ,
105- ) . resolves . toEqual ( expect . objectContaining ( { success : true } ) )
106- } finally {
107- await fs . rm ( workshopRoot , { recursive : true , force : true } )
108- }
96+ await using workshop = await createWorkshopFixture ( )
97+
98+ await expect (
99+ launchReadiness ( {
100+ workshopRoot : workshop . root ,
101+ silent : true ,
102+ skipRemote : true ,
103+ skipHead : true ,
104+ } ) ,
105+ ) . resolves . toEqual ( expect . objectContaining ( { success : true } ) )
109106} )
110107
111108test ( 'fails when epicshop.product.slug missing' , async ( ) => {
112- const workshopRoot = await createWorkshopFixture ( {
109+ await using workshop = await createWorkshopFixture ( {
113110 includeProductSlug : false ,
114111 } )
115112
116- try {
117- await expect (
118- launchReadiness ( {
119- workshopRoot,
120- silent : true ,
121- skipRemote : true ,
122- skipHead : true ,
123- } ) ,
124- ) . resolves . toEqual ( expect . objectContaining ( { success : false } ) )
125- } finally {
126- await fs . rm ( workshopRoot , { recursive : true , force : true } )
127- }
113+ await expect (
114+ launchReadiness ( {
115+ workshopRoot : workshop . root ,
116+ silent : true ,
117+ skipRemote : true ,
118+ skipHead : true ,
119+ } ) ,
120+ ) . resolves . toEqual ( expect . objectContaining ( { success : false } ) )
128121} )
129122
130123test ( 'fails when a required MDX file has no EpicVideo embed (and prints helpful path)' , async ( ) => {
131- const workshopRoot = await createWorkshopFixture ( )
124+ await using workshop = await createWorkshopFixture ( )
132125
133126 // Remove the EpicVideo embed from the step problem README.
134127 await writeFile (
135128 path . join (
136- workshopRoot ,
129+ workshop . root ,
137130 'exercises' ,
138131 '01.first-exercise' ,
139132 '01.problem' ,
@@ -144,30 +137,26 @@ test('fails when a required MDX file has no EpicVideo embed (and prints helpful
144137
145138 const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } )
146139
147- try {
148- const result = await launchReadiness ( {
149- workshopRoot,
150- silent : false ,
151- skipRemote : true ,
152- skipHead : true ,
153- } )
154-
155- expect ( result . success ) . toBe ( false )
156- const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
157- expect ( output ) . toContain ( 'No <EpicVideo url="..."> embed found' )
158- expect ( output ) . toContain (
159- 'exercises/01.first-exercise/01.problem/README.mdx' ,
160- )
161- } finally {
162- logSpy . mockRestore ( )
163- await fs . rm ( workshopRoot , { recursive : true , force : true } )
164- }
140+ const result = await launchReadiness ( {
141+ workshopRoot : workshop . root ,
142+ silent : false ,
143+ skipRemote : true ,
144+ skipHead : true ,
145+ } )
146+
147+ expect ( result . success ) . toBe ( false )
148+ const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
149+ expect ( output ) . toContain ( 'No <EpicVideo url="..."> embed found' )
150+ expect ( output ) . toContain ( 'exercises/01.first-exercise/01.problem/README.mdx' )
165151} )
166152
167153test ( 'remote lesson check fails when product lesson slug not represented locally' , async ( ) => {
168154 const productHost = 'www.epicweb.dev'
169155 const productSlug = 'test-workshop'
170- const workshopRoot = await createWorkshopFixture ( { productHost, productSlug } )
156+ await using workshop = await createWorkshopFixture ( {
157+ productHost,
158+ productSlug,
159+ } )
171160
172161 vi . stubGlobal (
173162 'fetch' ,
@@ -191,35 +180,33 @@ test('remote lesson check fails when product lesson slug not represented locally
191180
192181 const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } )
193182
194- try {
195- const result = await launchReadiness ( {
196- workshopRoot,
197- silent : false ,
198- skipRemote : false ,
199- skipHead : true ,
200- } )
201-
202- expect ( result . success ) . toBe ( false )
203- const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
204- expect ( output ) . toContain ( 'Missing videos in workshop for product lessons:' )
205- expect ( output ) . toContain ( 'missing-lesson' )
206- expect ( output ) . toContain (
207- `https://${ productHost } /workshops/${ productSlug } /functions-section/missing-lesson` ,
208- )
209- } finally {
210- logSpy . mockRestore ( )
211- await fs . rm ( workshopRoot , { recursive : true , force : true } )
212- }
183+ const result = await launchReadiness ( {
184+ workshopRoot : workshop . root ,
185+ silent : false ,
186+ skipRemote : false ,
187+ skipHead : true ,
188+ } )
189+
190+ expect ( result . success ) . toBe ( false )
191+ const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
192+ expect ( output ) . toContain ( 'Missing videos in workshop for product lessons:' )
193+ expect ( output ) . toContain ( 'missing-lesson' )
194+ expect ( output ) . toContain (
195+ `https://${ productHost } /workshops/${ productSlug } /functions-section/missing-lesson` ,
196+ )
213197} )
214198
215199test ( 'warns about extra embeds only for configured workshop (includes offending url + file)' , async ( ) => {
216200 const productHost = 'www.epicweb.dev'
217201 const productSlug = 'test-workshop'
218- const workshopRoot = await createWorkshopFixture ( { productHost, productSlug } )
202+ await using workshop = await createWorkshopFixture ( {
203+ productHost,
204+ productSlug,
205+ } )
219206
220207 // Add an extra embed for this workshop (should warn) and one outside /workshops (should not).
221208 const exerciseIntroPath = path . join (
222- workshopRoot ,
209+ workshop . root ,
223210 'exercises' ,
224211 '01.first-exercise' ,
225212 'README.mdx' ,
@@ -255,56 +242,46 @@ test('warns about extra embeds only for configured workshop (includes offending
255242
256243 const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } )
257244
258- try {
259- const result = await launchReadiness ( {
260- workshopRoot,
261- silent : false ,
262- skipRemote : false ,
263- skipHead : true ,
264- } )
265-
266- expect ( result . success ) . toBe ( true )
267- const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
268- expect ( output ) . toContain (
269- `EpicVideo embed not present in the product lesson list: https://${ productHost } /workshops/${ productSlug } /extra-lesson` ,
270- )
271- expect ( output ) . toContain ( 'exercises/01.first-exercise/README.mdx' )
272- expect ( output ) . not . toContain ( 'some-post' )
273- } finally {
274- logSpy . mockRestore ( )
275- await fs . rm ( workshopRoot , { recursive : true , force : true } )
276- }
245+ const result = await launchReadiness ( {
246+ workshopRoot : workshop . root ,
247+ silent : false ,
248+ skipRemote : false ,
249+ skipHead : true ,
250+ } )
251+
252+ expect ( result . success ) . toBe ( true )
253+ const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
254+ expect ( output ) . toContain (
255+ `EpicVideo embed not present in the product lesson list: https://${ productHost } /workshops/${ productSlug } /extra-lesson` ,
256+ )
257+ expect ( output ) . toContain ( 'exercises/01.first-exercise/README.mdx' )
258+ expect ( output ) . not . toContain ( 'some-post' )
277259} )
278260
279261test ( 'fails when a required FINISHED.mdx is too short' , async ( ) => {
280- const workshopRoot = await createWorkshopFixture ( )
262+ await using workshop = await createWorkshopFixture ( )
281263
282264 await writeFile (
283- path . join ( workshopRoot , 'exercises' , '01.first-exercise' , 'FINISHED.mdx' ) ,
265+ path . join ( workshop . root , 'exercises' , '01.first-exercise' , 'FINISHED.mdx' ) ,
284266 `Short.\n` ,
285267 )
286268
287269 const logSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } )
288270
289- try {
290- const result = await launchReadiness ( {
291- workshopRoot,
292- silent : false ,
293- skipRemote : true ,
294- skipHead : true ,
295- } )
296- expect ( result . success ) . toBe ( false )
297- const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
298- expect ( output ) . toContain ( 'File content too short' )
299- expect ( output ) . toContain ( 'exercises/01.first-exercise/FINISHED.mdx' )
300- } finally {
301- logSpy . mockRestore ( )
302- await fs . rm ( workshopRoot , { recursive : true , force : true } )
303- }
271+ const result = await launchReadiness ( {
272+ workshopRoot : workshop . root ,
273+ silent : false ,
274+ skipRemote : true ,
275+ skipHead : true ,
276+ } )
277+ expect ( result . success ) . toBe ( false )
278+ const output = logSpy . mock . calls . map ( ( c ) => c . join ( ' ' ) ) . join ( '\n' )
279+ expect ( output ) . toContain ( 'File content too short' )
280+ expect ( output ) . toContain ( 'exercises/01.first-exercise/FINISHED.mdx' )
304281} )
305282
306283test ( 'fails when an EpicVideo url does not return 200 to HEAD' , async ( ) => {
307- const workshopRoot = await createWorkshopFixture ( )
284+ await using workshop = await createWorkshopFixture ( )
308285
309286 vi . stubGlobal (
310287 'fetch' ,
@@ -317,16 +294,12 @@ test('fails when an EpicVideo url does not return 200 to HEAD', async () => {
317294 } ) ,
318295 )
319296
320- try {
321- await expect (
322- launchReadiness ( {
323- workshopRoot,
324- silent : true ,
325- skipRemote : true ,
326- skipHead : false ,
327- } ) ,
328- ) . resolves . toEqual ( expect . objectContaining ( { success : false } ) )
329- } finally {
330- await fs . rm ( workshopRoot , { recursive : true , force : true } )
331- }
297+ await expect (
298+ launchReadiness ( {
299+ workshopRoot : workshop . root ,
300+ silent : true ,
301+ skipRemote : true ,
302+ skipHead : false ,
303+ } ) ,
304+ ) . resolves . toEqual ( expect . objectContaining ( { success : false } ) )
332305} )
0 commit comments