@@ -2,6 +2,8 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
22import { rmSync , writeFileSync } from 'node:fs'
33import { join } from 'node:path'
44import { render } from '../../render/index.ts'
5+ import { createRenderer } from '../../render/createRenderer.ts'
6+ import { resolveConfig } from '../../config/index.ts'
57import { createTempProject } from './_helpers.ts'
68
79describe ( 'render' , ( ) => {
@@ -341,5 +343,83 @@ describe('render', () => {
341343 expect ( result . html ) . toContain ( 'data-marked' )
342344 expect ( result . html ) . toContain ( 'Everything works' )
343345 } )
346+
347+ it ( 'accepts plugins as a factory and calls it on every render' , async ( ) => {
348+ const resolvedConfig = await resolveConfig ( {
349+ root : tempDir ,
350+ } )
351+
352+ const renderer = await createRenderer ( { root : tempDir } )
353+
354+ try {
355+ let factoryCalls = 0
356+ const seenLabels : string [ ] = [ ]
357+
358+ const config = {
359+ ...resolvedConfig ,
360+ vue : {
361+ plugins : ( ) => {
362+ factoryCalls ++
363+ return [ {
364+ install ( app : any ) {
365+ app . config . globalProperties . $label = `instance-${ factoryCalls } `
366+ } ,
367+ } ]
368+ } ,
369+ } ,
370+ }
371+
372+ const a = await renderer . render ( `<template><div>{{ $label }}</div></template>` , config )
373+ const b = await renderer . render ( `<template><div>{{ $label }}</div></template>` , config )
374+
375+ expect ( factoryCalls ) . toBe ( 2 )
376+ seenLabels . push ( a . html , b . html )
377+ expect ( seenLabels [ 0 ] ) . toContain ( 'instance-1' )
378+ expect ( seenLabels [ 1 ] ) . toContain ( 'instance-2' )
379+ } finally {
380+ await renderer . close ( )
381+ }
382+ } )
383+
384+ it ( 'isolates stateful plugins across renders (no shared state leak)' , async ( ) => {
385+ const resolvedConfig = await resolveConfig ( {
386+ root : tempDir ,
387+ } )
388+
389+ const renderer = await createRenderer ( { root : tempDir } )
390+
391+ try {
392+ // A stateful plugin: each instance owns its own mutable counter
393+ // exposed via $counter. If the framework reused one instance
394+ // across renders, the second render would observe `1` instead of `0`.
395+ const config = {
396+ ...resolvedConfig ,
397+ vue : {
398+ plugins : ( ) => {
399+ const state = { value : 0 }
400+ return [ {
401+ install ( app : any ) {
402+ app . config . globalProperties . $counter = state
403+ app . config . globalProperties . $bump = ( ) => { state . value ++ }
404+ } ,
405+ } ]
406+ } ,
407+ } ,
408+ }
409+
410+ const first = await renderer . render ( `
411+ <template><div>{{ ($bump(), $counter.value) }}</div></template>
412+ ` , config )
413+
414+ const second = await renderer . render ( `
415+ <template><div>{{ $counter.value }}</div></template>
416+ ` , config )
417+
418+ expect ( first . html ) . toContain ( '>1<' )
419+ expect ( second . html ) . toContain ( '>0<' )
420+ } finally {
421+ await renderer . close ( )
422+ }
423+ } )
344424 } )
345425} )
0 commit comments