|
| 1 | +import{EventEmitter}from'node:events' |
| 2 | +importprocessfrom'node:process' |
| 3 | + |
| 4 | +import{afterEach,beforeEach,describe,expect,it,vi}from'vitest' |
| 5 | + |
| 6 | +const{ closeListener, closeNuxt, closeWatchers, devServers, releaseLock, startCpuProfile, stopCpuProfile }=vi.hoisted(()=>({ |
| 7 | +closeListener: vi.fn(()=>Promise.resolve()), |
| 8 | +closeNuxt: vi.fn(()=>Promise.resolve()), |
| 9 | +closeWatchers: vi.fn(), |
| 10 | +devServers: []asany[], |
| 11 | +releaseLock: vi.fn(), |
| 12 | +startCpuProfile: vi.fn(()=>Promise.resolve()), |
| 13 | +stopCpuProfile: vi.fn(), |
| 14 | +})) |
| 15 | + |
| 16 | +vi.mock('../../../src/utils/profile.ts',()=>({ startCpuProfile, stopCpuProfile })) |
| 17 | +vi.mock('../../../src/utils/env.ts',()=>({overrideEnv: vi.fn()})) |
| 18 | + |
| 19 | +vi.mock('../../../src/dev/utils',()=>({ |
| 20 | +NuxtDevServer: classextendsEventEmitter{ |
| 21 | +listener={url: 'http://127.0.0.1:3000',close: closeListener} |
| 22 | +closeWatchers=closeWatchers |
| 23 | +close=closeNuxt |
| 24 | +releaseLock=releaseLock |
| 25 | +load=vi.fn(()=>Promise.resolve()) |
| 26 | + |
| 27 | +constructor(readonlyoptions: Record<string,any>){ |
| 28 | +super() |
| 29 | +devServers.push(this) |
| 30 | +} |
| 31 | + |
| 32 | +init=vi.fn(async()=>{ |
| 33 | +this.emit('ready','http://127.0.0.1:3000') |
| 34 | +}) |
| 35 | +}, |
| 36 | +})) |
| 37 | + |
| 38 | +const{ initialize }=awaitimport('../../../src/dev/index') |
| 39 | + |
| 40 | +functioncontext(args: Record<string,unknown>={}){ |
| 41 | +return{cwd: process.cwd(),args: { ...args}}asnever |
| 42 | +} |
| 43 | + |
| 44 | +beforeEach(()=>{ |
| 45 | +devServers.length=0 |
| 46 | +vi.clearAllMocks() |
| 47 | +}) |
| 48 | + |
| 49 | +afterEach(()=>{ |
| 50 | +vi.restoreAllMocks() |
| 51 | +}) |
| 52 | + |
| 53 | +describe('initialize',()=>{ |
| 54 | +it('should report the address to a callback registered after ready',async()=>{ |
| 55 | +const{ onReady }=awaitinitialize(context()) |
| 56 | +constseen: string[]=[] |
| 57 | + |
| 58 | +onReady(address=>seen.push(address)) |
| 59 | + |
| 60 | +expect(seen).toEqual(['http://127.0.0.1:3000']) |
| 61 | +}) |
| 62 | + |
| 63 | +it('should close the watchers, the listener and nuxt before releasing the lock',async()=>{ |
| 64 | +constorder: string[]=[] |
| 65 | +closeWatchers.mockImplementation(()=>order.push('watchers')) |
| 66 | +closeListener.mockImplementation(async()=>voidorder.push('listener')) |
| 67 | +closeNuxt.mockImplementation(async()=>voidorder.push('nuxt')) |
| 68 | +releaseLock.mockImplementation(()=>order.push('lock')) |
| 69 | + |
| 70 | +const{ close }=awaitinitialize(context()) |
| 71 | +awaitclose() |
| 72 | + |
| 73 | +expect(order[0]).toBe('watchers') |
| 74 | +expect(order.at(-1)).toBe('lock') |
| 75 | +expect(order).toContain('listener') |
| 76 | +expect(order).toContain('nuxt') |
| 77 | +}) |
| 78 | + |
| 79 | +it('should close only once however often it is asked',async()=>{ |
| 80 | +const{ close }=awaitinitialize(context()) |
| 81 | + |
| 82 | +awaitPromise.all([close(),close()]) |
| 83 | +awaitclose() |
| 84 | + |
| 85 | +expect(releaseLock).toHaveBeenCalledTimes(1) |
| 86 | +expect(closeListener).toHaveBeenCalledTimes(1) |
| 87 | +}) |
| 88 | + |
| 89 | +it('should release the lock even when closing fails',async()=>{ |
| 90 | +closeNuxt.mockRejectedValue(newError('nitro would not close')) |
| 91 | + |
| 92 | +const{ close }=awaitinitialize(context()) |
| 93 | + |
| 94 | +awaitexpect(close()).rejects.toThrow('nitro would not close') |
| 95 | +expect(releaseLock).toHaveBeenCalledTimes(1) |
| 96 | +}) |
| 97 | + |
| 98 | +it('should reload in place when asked',async()=>{ |
| 99 | +const{ reload }=awaitinitialize(context()) |
| 100 | + |
| 101 | +awaitreload({type: 'shortcut'}) |
| 102 | + |
| 103 | +expect(devServers[0].load).toHaveBeenCalledWith(true,{type: 'shortcut'}) |
| 104 | +}) |
| 105 | + |
| 106 | +it('should pass `extends` through to the dev server overrides',async()=>{ |
| 107 | +awaitinitialize(context({extends: ['../layer']})) |
| 108 | + |
| 109 | +expect(devServers[0].options.overrides).toMatchObject({extends: ['../layer']}) |
| 110 | +}) |
| 111 | + |
| 112 | +it('should not profile unless it was asked to',async()=>{ |
| 113 | +awaitinitialize(context()) |
| 114 | + |
| 115 | +expect(startCpuProfile).not.toHaveBeenCalled() |
| 116 | +expect(devServers[0].options.overrides.debug).toBeUndefined() |
| 117 | +}) |
| 118 | + |
| 119 | +it('should profile quietly for a bare `--profile`',async()=>{ |
| 120 | +awaitinitialize(context({profile: ''})) |
| 121 | + |
| 122 | +expect(startCpuProfile).toHaveBeenCalledTimes(1) |
| 123 | +expect(devServers[0].options.overrides).toMatchObject({debug: {perf: 'quiet'}}) |
| 124 | +}) |
| 125 | + |
| 126 | +it('should profile verbosely for `--profile=verbose`',async()=>{ |
| 127 | +awaitinitialize(context({profile: 'verbose'})) |
| 128 | + |
| 129 | +expect(devServers[0].options.overrides).toMatchObject({debug: {perf: true}}) |
| 130 | +}) |
| 131 | + |
| 132 | +it('should hand the file change and restart hooks to the caller',async()=>{ |
| 133 | +const{ onFileChange, onRestart }=awaitinitialize(context()) |
| 134 | +constchanged=vi.fn() |
| 135 | +constrestarted=vi.fn() |
| 136 | + |
| 137 | +onFileChange(changed) |
| 138 | +onRestart(restarted) |
| 139 | +devServers[0].emit('change') |
| 140 | +devServers[0].emit('restart',{type: 'hook'}) |
| 141 | + |
| 142 | +expect(changed).toHaveBeenCalledTimes(1) |
| 143 | +expect(restarted).toHaveBeenCalledWith({type: 'hook'}) |
| 144 | +}) |
| 145 | +}) |
0 commit comments