Skip to content

Commit 223508b

Browse files
Merge pull request #64 from HichemTab-tech/add-integration-tests
Add tests for plugins, transformers, and integration
2 parents b381b66 + ee5d7fc commit 223508b

8 files changed

Lines changed: 772 additions & 5 deletions

package-lock.json

Lines changed: 429 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
}
2121
},
2222
"scripts": {
23-
"build": "vite build && node add-banner.js"
23+
"build": "vite build && node add-banner.js",
24+
"test": "vitest run",
25+
"test:watch": "vitest"
2426
},
2527
"keywords": [
2628
"typescript",
@@ -40,6 +42,7 @@
4042
"ts-loader": "^9.5.1",
4143
"typescript": "^5.0.0",
4244
"vite": "^6.0.3",
45+
"vitest": "^3.1.4",
4346
"webpack": "^5.97.1",
4447
"webpack-cli": "^6.0.1"
4548
}

test/index.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createPicker, createFullPicker } from '../src';
3+
4+
describe('index', () => {
5+
describe('createPicker', () => {
6+
it('should throw an error when called directly', () => {
7+
expect(() => {
8+
createPicker<{ name: string }>()({});
9+
}).toThrow('createPicker is a placeholder. Use the ts-runtime-picker plugin to transform it during build.');
10+
});
11+
});
12+
13+
describe('createFullPicker', () => {
14+
it('should throw an error when called directly', () => {
15+
expect(() => {
16+
createFullPicker<{ name: string }>()({});
17+
}).toThrow('createFullPicker is a placeholder. Use the ts-runtime-picker plugin to transform it during build.');
18+
});
19+
});
20+
});

test/integration.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { transform } from '../src/ts-transformer';
3+
4+
describe('Integration', () => {
5+
it('should transform createPicker to pick only specified properties', () => {
6+
// noinspection NpmUsedModulesInstalled,JSUnresolvedReference,UnnecessaryLabelJS,BadExpressionStatementJS,JSValidateTypes,JSUnusedLocalSymbols
7+
const code = `
8+
// noinspection JSAnnotator
9+
10+
import { createPicker } from "ts-runtime-picker";
11+
12+
interface User {
13+
firstName: string;
14+
lastName: string;
15+
email: string;
16+
password: string;
17+
}
18+
19+
const picker = createPicker<User>();
20+
21+
const user = {
22+
firstName: "John",
23+
lastName: "Doe",
24+
email: "john.doe@example.com",
25+
password: "secret",
26+
extraData: "will be removed"
27+
};
28+
29+
const result = picker(user);
30+
`;
31+
32+
const filePath = 'test-file.ts';
33+
const transformedCode = transform(code, filePath);
34+
35+
// Verify the transformation
36+
expect(transformedCode).toContain('const _keys: string[] = ["firstName","lastName","email","password"]');
37+
expect(transformedCode).not.toContain('createPicker<User>()');
38+
39+
// Instead of executing the code (which would fail due to import statements),
40+
// we'll verify the structure of the transformed code
41+
42+
// Check that the picker function is correctly transformed
43+
expect(transformedCode).toContain('const picker = (_obj: any) => {');
44+
expect(transformedCode).toContain('const _keys: string[] = ["firstName","lastName","email","password"]');
45+
expect(transformedCode).toContain('return _keys.reduce((_acc: {[k: string]: any}, _key: string) => {');
46+
expect(transformedCode).toContain('if (_key in _obj) _acc[_key] = _obj[_key];');
47+
expect(transformedCode).toContain('return _acc;');
48+
expect(transformedCode).toContain('}, {});');
49+
});
50+
51+
it('should transform createFullPicker to pick all properties', () => {
52+
// noinspection NpmUsedModulesInstalled,JSUnresolvedReference,UnnecessaryLabelJS,BadExpressionStatementJS,JSValidateTypes,JSUnusedLocalSymbols
53+
const code = `
54+
// noinspection JSAnnotator
55+
56+
import { createFullPicker } from "ts-runtime-picker";
57+
58+
interface User {
59+
firstName: string;
60+
lastName: string;
61+
}
62+
63+
const picker = createFullPicker<User>();
64+
65+
const user = {
66+
firstName: "John",
67+
lastName: "Doe",
68+
extraData: "will be removed"
69+
};
70+
71+
const result = picker(user);
72+
`;
73+
74+
const filePath = 'test-file.ts';
75+
const transformedCode = transform(code, filePath);
76+
77+
// Verify the transformation
78+
expect(transformedCode).toContain('const _keys: string[] = ["firstName","lastName"]');
79+
expect(transformedCode).not.toContain('createFullPicker<User>()');
80+
81+
// Instead of executing the code (which would fail due to import statements),
82+
// we'll verify the structure of the transformed code
83+
84+
// Check that the picker function is correctly transformed
85+
expect(transformedCode).toContain('const picker = (_obj: any) => {');
86+
expect(transformedCode).toContain('const _keys: string[] = ["firstName","lastName"]');
87+
expect(transformedCode).toContain('return _keys.reduce((_acc: {[k: string]: any}, _key: string) => {');
88+
expect(transformedCode).toContain('if (_key in _obj) _acc[_key] = _obj[_key];');
89+
expect(transformedCode).toContain('return _acc;');
90+
expect(transformedCode).toContain('}, {});');
91+
});
92+
});

test/ts-transformer.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { transform } from '../src/ts-transformer';
3+
4+
describe('ts-transformer', () => {
5+
it('should transform createPicker calls', () => {
6+
const code = `
7+
import { createPicker } from "ts-runtime-picker";
8+
9+
interface User {
10+
firstName: string;
11+
lastName: string;
12+
email: string;
13+
}
14+
15+
const picker = createPicker<User>();
16+
`;
17+
18+
const filePath = 'test-file.ts';
19+
const result = transform(code, filePath);
20+
21+
// The transformed code should contain the runtime implementation
22+
expect(result).toContain('const picker = (_obj: any) => {');
23+
expect(result).toContain('const _keys: string[] = ["firstName","lastName","email"]');
24+
expect(result).toContain('if (_key in _obj) _acc[_key] = _obj[_key];');
25+
});
26+
27+
it('should handle aliased imports', () => {
28+
const code = `
29+
import { createPicker as cp } from "ts-runtime-picker";
30+
31+
interface User {
32+
firstName: string;
33+
lastName: string;
34+
}
35+
36+
const picker = cp<User>();
37+
`;
38+
39+
const filePath = 'test-file.ts';
40+
const result = transform(code, filePath);
41+
42+
// The transformed code should contain the runtime implementation
43+
expect(result).toContain('const picker = (_obj: any) => {');
44+
expect(result).toContain('const _keys: string[] = ["firstName","lastName"]');
45+
});
46+
47+
it('should not transform code without createPicker imports', () => {
48+
const code = `
49+
interface User {
50+
firstName: string;
51+
lastName: string;
52+
}
53+
54+
const picker = someOtherFunction<User>();
55+
`;
56+
57+
const filePath = 'test-file.ts';
58+
const result = transform(code, filePath);
59+
60+
// The code should remain unchanged
61+
expect(result).toBe(code);
62+
});
63+
64+
it('should transform createFullPicker calls', () => {
65+
const code = `
66+
import { createFullPicker } from "ts-runtime-picker";
67+
68+
interface User {
69+
firstName: string;
70+
lastName: string;
71+
email: string;
72+
}
73+
74+
const picker = createFullPicker<User>();
75+
`;
76+
77+
const filePath = 'test-file.ts';
78+
const result = transform(code, filePath);
79+
80+
// The transformed code should contain the runtime implementation
81+
expect(result).toContain('const picker = (_obj: any) => {');
82+
expect(result).toContain('const _keys: string[] = ["firstName","lastName","email"]');
83+
expect(result).toContain('if (_key in _obj) _acc[_key] = _obj[_key];');
84+
});
85+
});

test/vite-plugin.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { TsRuntimePickerVitePlugin } from '../src/vite-plugin';
3+
import * as transformer from '../src/ts-transformer';
4+
5+
// Mock the transform function
6+
vi.mock('../src/ts-transformer', () => ({
7+
transform: vi.fn((code) => `transformed: ${code}`)
8+
}));
9+
10+
beforeEach(() => {
11+
vi.clearAllMocks();
12+
});
13+
14+
describe('TsRuntimePickerVitePlugin', () => {
15+
it('should create a Vite plugin with the correct configuration', () => {
16+
const plugin = TsRuntimePickerVitePlugin();
17+
18+
expect(plugin.name).toBe('vite-plugin-ts-runtime-picker');
19+
expect(plugin.enforce).toBe('pre');
20+
expect(typeof plugin.transform).toBe('function');
21+
});
22+
23+
it('should transform TypeScript files', () => {
24+
const plugin = TsRuntimePickerVitePlugin();
25+
const code = 'const x = 1;';
26+
const id = 'file.ts';
27+
28+
const result = plugin.transform(code, id);
29+
30+
expect(transformer.transform).toHaveBeenCalledWith(code, id);
31+
expect(result).toEqual({
32+
code: `transformed: ${code}`,
33+
map: null
34+
});
35+
});
36+
37+
it('should transform TypeScript JSX files', () => {
38+
const plugin = TsRuntimePickerVitePlugin();
39+
const code = 'const x = <div />;';
40+
const id = 'file.tsx';
41+
42+
const result = plugin.transform(code, id);
43+
44+
expect(transformer.transform).toHaveBeenCalledWith(code, id);
45+
expect(result).toEqual({
46+
code: `transformed: ${code}`,
47+
map: null
48+
});
49+
});
50+
51+
it('should not transform non-TypeScript files', () => {
52+
const plugin = TsRuntimePickerVitePlugin();
53+
const code = 'const x = 1;';
54+
const id = 'file.js';
55+
56+
const result = plugin.transform(code, id);
57+
58+
expect(transformer.transform).not.toHaveBeenCalled();
59+
expect(result).toBeNull();
60+
});
61+
});

test/webpack-loader.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import TsRuntimePickerWebpackLoader from '../src/webpack-loader';
3+
import * as transformer from '../src/ts-transformer';
4+
5+
// Mock the transform function
6+
vi.mock('../src/ts-transformer', () => ({
7+
transform: vi.fn((code) => `transformed: ${code}`)
8+
}));
9+
10+
describe('TsRuntimePickerWebpackLoader', () => {
11+
let loaderContext: any;
12+
let callback: any;
13+
14+
beforeEach(() => {
15+
// Reset mocks
16+
vi.clearAllMocks();
17+
18+
// Create a mock loader context
19+
callback = vi.fn();
20+
loaderContext = {
21+
resourcePath: 'file.ts',
22+
async: () => callback
23+
};
24+
});
25+
26+
it('should transform TypeScript files', () => {
27+
const source = 'const x = 1;';
28+
const map = { version: 3, sources: [], mappings: '' };
29+
30+
TsRuntimePickerWebpackLoader.call(loaderContext, source, map);
31+
32+
expect(transformer.transform).toHaveBeenCalledWith(source, 'file.ts');
33+
expect(callback).toHaveBeenCalledWith(null, `transformed: ${source}`, map);
34+
});
35+
36+
it('should transform TypeScript JSX files', () => {
37+
loaderContext.resourcePath = 'file.tsx';
38+
const source = 'const x = <div />;';
39+
const map = { version: 3, sources: [], mappings: '' };
40+
41+
TsRuntimePickerWebpackLoader.call(loaderContext, source, map);
42+
43+
expect(transformer.transform).toHaveBeenCalledWith(source, 'file.tsx');
44+
expect(callback).toHaveBeenCalledWith(null, `transformed: ${source}`, map);
45+
});
46+
47+
it('should handle errors during transformation', () => {
48+
const error = new Error('Transformation error');
49+
(transformer.transform as any).mockImplementationOnce(() => {
50+
throw error;
51+
});
52+
53+
const source = 'const x = 1;';
54+
const map = { version: 3, sources: [], mappings: '' };
55+
56+
TsRuntimePickerWebpackLoader.call(loaderContext, source, map);
57+
58+
expect(transformer.transform).toHaveBeenCalledWith(source, 'file.ts');
59+
expect(callback).toHaveBeenCalledWith(error);
60+
});
61+
62+
it('should not transform non-TypeScript files', () => {
63+
loaderContext.resourcePath = 'file.js';
64+
const source = 'const x = 1;';
65+
const map = { version: 3, sources: [], mappings: '' };
66+
67+
TsRuntimePickerWebpackLoader.call(loaderContext, source, map);
68+
69+
expect(transformer.transform).not.toHaveBeenCalled();
70+
// The callback should not be called for non-TypeScript files
71+
expect(callback).not.toHaveBeenCalled();
72+
});
73+
});

vite.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { defineConfig } from 'vite';
22
import { resolve } from 'path';
3+
import { configDefaults } from 'vitest/config';
34

45
// noinspection JSUnusedGlobalSymbols
56
export default defineConfig({
7+
test: {
8+
globals: true,
9+
environment: 'node',
10+
include: ['**/*.{test,spec}.{js,ts}'],
11+
exclude: [...configDefaults.exclude, 'dist/**']
12+
},
613
build: {
714
lib: {
815
// Define multiple entry points
@@ -37,4 +44,4 @@ export default defineConfig({
3744
}
3845
}
3946
]
40-
});
47+
});

0 commit comments

Comments
 (0)