diff --git a/tests/plugins/esbuild.test.ts b/tests/plugins/esbuild.test.ts new file mode 100644 index 0000000..58d720b --- /dev/null +++ b/tests/plugins/esbuild.test.ts @@ -0,0 +1,150 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { yarnSpinnerPlugin } from '../../src/plugins/esbuild'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +describe('yarnSpinnerPlugin (esbuild)', () => { + // Fixtures are in tests/fixtures/, not tests/plugins/fixtures/ + const projectPath = resolve(__dirname, '../fixtures/simple/.yarnproject'); + + it('should return an esbuild plugin with correct name', () => { + const plugin = yarnSpinnerPlugin(); + expect(plugin.name).toBe('yarn-spinner-loader'); + }); + + it('should have a setup function', () => { + const plugin = yarnSpinnerPlugin(); + expect(plugin.setup).toBeDefined(); + expect(typeof plugin.setup).toBe('function'); + }); + + it('should register onLoad handler for .yarnproject files', () => { + const plugin = yarnSpinnerPlugin(); + + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + // Store the callback for testing + (mockBuild as any)._onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + expect(mockBuild.onLoad).toHaveBeenCalledWith( + { filter: /\.yarnproject$/ }, + expect.any(Function), + ); + }); + + it('should load .yarnproject files and return correct content', async () => { + const plugin = yarnSpinnerPlugin(); + + let onLoadCallback: Function | undefined; + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + // Ensure callback was registered + expect(onLoadCallback).toBeDefined(); + + // Simulate esbuild calling the callback + const args = { path: projectPath }; + const result = await (onLoadCallback as any)(args); + + expect(result).toBeDefined(); + expect(result.contents).toContain('export default'); + expect(result.contents).toContain('project'); + expect(result.contents).toContain('yarnFiles'); + expect(result.loader).toBe('js'); + }); + + it('should handle errors by returning error object', async () => { + const plugin = yarnSpinnerPlugin(); + + let onLoadCallback: Function | undefined; + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + // Simulate esbuild calling the callback with non-existent file + const args = { path: 'nonexistent/.yarnproject' }; + const result = await (onLoadCallback as any)(args); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toBeGreaterThan(0); + expect(result.errors[0].text).toBeDefined(); + expect(typeof result.errors[0].text).toBe('string'); + }); + + it('should accept loadOptions and pass them to loadYarnProject', async () => { + const plugin = yarnSpinnerPlugin({ + loadOptions: { + baseDir: resolve(__dirname, 'fixtures/simple'), + }, + }); + + let onLoadCallback: Function | undefined; + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + const args = { path: projectPath }; + const result = await (onLoadCallback as any)(args); + + expect(result.contents).toContain('export default'); + expect(result.loader).toBe('js'); + }); + + it('should correctly serialize the result as JSON with proper formatting', async () => { + const plugin = yarnSpinnerPlugin(); + + let onLoadCallback: Function | undefined; + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + const args = { path: projectPath }; + const result = await (onLoadCallback as any)(args); + + // The contents should be properly formatted JavaScript code + expect(result.contents).toMatch(/export default {[\s\S]*};/); + expect(result.loader).toBe('js'); + }); + + it('should handle non-Error objects in error handling', async () => { + const plugin = yarnSpinnerPlugin(); + + let onLoadCallback: Function | undefined; + const mockBuild = { + onLoad: vi.fn((_options, callback) => { + onLoadCallback = callback; + }), + }; + + plugin.setup(mockBuild as any); + + const args = { path: 'nonexistent/.yarnproject' }; + const result = await (onLoadCallback as any)(args); + + expect(result.errors[0].text).toBeDefined(); + expect(typeof result.errors[0].text).toBe('string'); + }); +}); diff --git a/tests/plugins/rollup.test.ts b/tests/plugins/rollup.test.ts new file mode 100644 index 0000000..5597f71 --- /dev/null +++ b/tests/plugins/rollup.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi } from 'vitest'; +import { yarnSpinnerRollup } from '../../src/plugins/rollup'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +describe('yarnSpinnerRollup', () => { + // Fixtures are in tests/fixtures/, not tests/plugins/fixtures/ + const projectPath = resolve(__dirname, '../fixtures/simple/.yarnproject'); + + it('should return a Rollup plugin with correct name', () => { + const plugin = yarnSpinnerRollup(); + expect(plugin.name).toBe('yarn-spinner-loader'); + }); + + it('should have a load function', () => { + const plugin = yarnSpinnerRollup(); + expect(plugin.load).toBeDefined(); + expect(typeof plugin.load).toBe('function'); + }); + + it('should load .yarnproject files and return exported code', async () => { + const plugin = yarnSpinnerRollup(); + const loadFn = plugin.load as Function; + + const mockContext = { + error: vi.fn(), + }; + + const result = await loadFn.call(mockContext, projectPath); + + // If error was called, the file wasn't found or was invalid + if (mockContext.error.mock.calls.length > 0) { + throw new Error(mockContext.error.mock.calls[0][0]); + } + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('export default'); + expect(result).toContain('project'); + expect(result).toContain('yarnFiles'); + }); + + it('should return null for non-.yarnproject files', async () => { + const plugin = yarnSpinnerRollup(); + const loadFn = plugin.load as Function; + + const result = await loadFn.call({}, 'some-file.ts'); + + expect(result).toBeNull(); + }); + + it('should handle errors by calling this.error', async () => { + const plugin = yarnSpinnerRollup(); + const loadFn = plugin.load as Function; + + const mockThis = { + error: vi.fn(), + }; + + await loadFn.call(mockThis, 'nonexistent/.yarnproject'); + + expect(mockThis.error).toHaveBeenCalled(); + }); + + it('should accept loadOptions and pass them to loadYarnProject', async () => { + const plugin = yarnSpinnerRollup({ + loadOptions: { + baseDir: resolve(__dirname, '../fixtures/simple'), + }, + }); + const loadFn = plugin.load as Function; + + const mockContext = { + error: vi.fn(), + }; + + const result = await loadFn.call(mockContext, projectPath); + + expect(mockContext.error).not.toHaveBeenCalled(); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('export default'); + }); + + it('should correctly serialize the result as JSON', async () => { + const plugin = yarnSpinnerRollup(); + const loadFn = plugin.load as Function; + + const mockContext = { + error: vi.fn(), + }; + + const result = await loadFn.call(mockContext, projectPath); + + if (mockContext.error.mock.calls.length > 0) { + throw new Error(mockContext.error.mock.calls[0][0]); + } + + // The result should be valid JavaScript code + expect(result).toMatch(/export default {[\s\S]*};/); + }); +}); diff --git a/tests/plugins/vite.test.ts b/tests/plugins/vite.test.ts new file mode 100644 index 0000000..ecc653c --- /dev/null +++ b/tests/plugins/vite.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi } from 'vitest'; +import { yarnSpinnerVite } from '../../src/plugins/vite'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +describe('yarnSpinnerVite', () => { + // Fixtures are in tests/fixtures/, not tests/plugins/fixtures/ + const projectPath = resolve(__dirname, '../fixtures/simple/.yarnproject'); + + it('should return a Vite plugin with correct name', () => { + const plugin = yarnSpinnerVite(); + expect(plugin.name).toBe('yarn-spinner-loader'); + }); + + it('should have a load function', () => { + const plugin = yarnSpinnerVite(); + expect(plugin.load).toBeDefined(); + expect(typeof plugin.load).toBe('function'); + }); + + it('should load .yarnproject files and return exported code', async () => { + const plugin = yarnSpinnerVite(); + const loadFn = plugin.load as Function; + + const mockContext = { + error: vi.fn(), + }; + + const result = await loadFn.call(mockContext, projectPath); + + // If error was called, the file wasn't found or was invalid + if (mockContext.error.mock.calls.length > 0) { + throw new Error(mockContext.error.mock.calls[0][0]); + } + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('export default'); + expect(result).toContain('project'); + expect(result).toContain('yarnFiles'); + }); + + it('should return null for non-.yarnproject files', async () => { + const plugin = yarnSpinnerVite(); + const loadFn = plugin.load as Function; + + const result = await loadFn.call({}, 'some-file.ts'); + + expect(result).toBeNull(); + }); + + it('should handle errors by calling this.error', async () => { + const plugin = yarnSpinnerVite(); + const loadFn = plugin.load as Function; + + const mockThis = { + error: vi.fn(), + }; + + await loadFn.call(mockThis, 'nonexistent/.yarnproject'); + + expect(mockThis.error).toHaveBeenCalled(); + }); + + it('should accept loadOptions and pass them to loadYarnProject', async () => { + const plugin = yarnSpinnerVite({ + loadOptions: { + baseDir: resolve(__dirname, '../fixtures/simple'), + }, + }); + const loadFn = plugin.load as Function; + + const mockContext = { + error: vi.fn(), + }; + + const result = await loadFn.call(mockContext, projectPath); + + expect(mockContext.error).not.toHaveBeenCalled(); + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('export default'); + }); + + it('should have a transform function', () => { + const plugin = yarnSpinnerVite(); + expect(plugin.transform).toBeDefined(); + expect(typeof plugin.transform).toBe('function'); + }); + + it('transform should return empty export for .yarnproject files', () => { + const plugin = yarnSpinnerVite(); + const transformFn = plugin.transform as Function; + + const result = transformFn.call({}, 'console.log("test")', projectPath); + + expect(result).toEqual({ + code: 'export default {};', + map: null, + }); + }); + + it('transform should return null for non-.yarnproject files', () => { + const plugin = yarnSpinnerVite(); + const transformFn = plugin.transform as Function; + + const result = transformFn.call({}, 'console.log("test")', 'some-file.ts'); + + expect(result).toBeNull(); + }); +}); diff --git a/tests/plugins/webpack.test.ts b/tests/plugins/webpack.test.ts new file mode 100644 index 0000000..ad529e8 --- /dev/null +++ b/tests/plugins/webpack.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect, vi } from 'vitest'; +import { YarnSpinnerWebpackLoader } from '../../src/plugins/webpack'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +describe('YarnSpinnerWebpackLoader', () => { + // Fixtures are in tests/fixtures/, not tests/plugins/fixtures/ + const projectPath = resolve(__dirname, '../fixtures/simple/.yarnproject'); + + it('should be a function', () => { + expect(typeof YarnSpinnerWebpackLoader).toBe('function'); + }); + + it('should load .yarnproject files and return exported JSON', function () { + const mockContext = { + getOptions: vi.fn().mockReturnValue({}), + resourcePath: projectPath, + emitError: vi.fn(), + }; + + const result = YarnSpinnerWebpackLoader.call(mockContext, ''); + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + expect(result).toContain('export default'); + expect(result).toContain('project'); + expect(result).toContain('yarnFiles'); + // Ensure no errors were emitted + expect(mockContext.emitError).not.toHaveBeenCalled(); + }); + + it('should pass loadOptions to loadYarnProjectSync', function () { + const mockContext = { + getOptions: vi.fn().mockReturnValue({ + loadOptions: { + baseDir: resolve(__dirname, 'fixtures/simple'), + }, + }), + resourcePath: projectPath, + emitError: vi.fn(), + }; + + const result = YarnSpinnerWebpackLoader.call(mockContext, ''); + + expect(result).toContain('export default'); + expect(mockContext.emitError).not.toHaveBeenCalled(); + }); + + it('should handle errors by calling emitError', function () { + const mockContext = { + getOptions: vi.fn().mockReturnValue({}), + resourcePath: 'nonexistent/.yarnproject', + emitError: vi.fn(), + }; + + const originalSource = '// original source'; + const result = YarnSpinnerWebpackLoader.call(mockContext, originalSource); + + expect(mockContext.emitError).toHaveBeenCalled(); + expect(mockContext.emitError).toHaveBeenCalledWith(expect.any(Error)); + // Should return original source on error + expect(result).toBe(originalSource); + }); + + it('should return original source on error', function () { + const originalSource = '// original source'; + const mockContext = { + getOptions: vi.fn().mockReturnValue({}), + resourcePath: 'nonexistent/.yarnproject', + emitError: vi.fn(), + }; + + const result = YarnSpinnerWebpackLoader.call(mockContext, originalSource); + + expect(result).toBe(originalSource); + }); + + it('should handle non-Error objects in error handling', function () { + const mockContext = { + getOptions: vi.fn().mockReturnValue({}), + resourcePath: 'nonexistent/.yarnproject', + emitError: vi.fn(), + }; + + YarnSpinnerWebpackLoader.call(mockContext, ''); + + expect(mockContext.emitError).toHaveBeenCalled(); + const emittedError = mockContext.emitError.mock.calls[0][0]; + expect(emittedError).toBeInstanceOf(Error); + }); + + it('should have a default export', () => { + // Import using ESM to check default export + expect(YarnSpinnerWebpackLoader).toBeDefined(); + }); +});