@uarlouski/testrail-mcp-server 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/client/testrail.js +167 -0
- package/{src/index.ts → dist/index.js} +35 -35
- package/dist/tools/add_attachment_to_run.js +52 -0
- package/dist/tools/add_results.js +24 -0
- package/dist/tools/add_run.js +19 -0
- package/dist/tools/create_case.js +23 -0
- package/{src/tools/get_case.ts → dist/tools/get_case.js} +3 -12
- package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
- package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +10 -24
- package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +3 -10
- package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +4 -11
- package/dist/tools/get_statuses.js +13 -0
- package/dist/tools/get_templates.js +16 -0
- package/dist/tools/get_tests.js +17 -0
- package/{src/tools/update_case.ts → dist/tools/update_case.js} +2 -8
- package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +4 -8
- package/dist/types/custom.js +1 -0
- package/{src/types/testrail.ts → dist/types/testrail.js} +37 -24
- package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
- package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
- package/package.json +10 -2
- package/.env.example +0 -3
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -35
- package/CHANGELOG.md +0 -27
- package/jest.config.js +0 -24
- package/src/client/testrail.ts +0 -161
- package/src/tools/create_case.ts +0 -29
- package/src/tools/get_templates.ts +0 -23
- package/src/types/custom.ts +0 -19
- package/test/client/testrail.test.ts +0 -430
- package/test/tools/create_case.test.ts +0 -101
- package/test/tools/get_case.test.ts +0 -185
- package/test/tools/get_case_fields.test.ts +0 -132
- package/test/tools/get_cases.test.ts +0 -104
- package/test/tools/get_projects.test.ts +0 -38
- package/test/tools/get_sections.test.ts +0 -39
- package/test/tools/get_templates.test.ts +0 -38
- package/test/tools/update_case.test.ts +0 -102
- package/test/tools/update_cases.test.ts +0 -61
- package/test/utils/mapper.test.ts +0 -112
- package/test/utils/sanitizer.test.ts +0 -143
- package/tsconfig.json +0 -24
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import { updateCaseTool } from '../../src/tools/update_case.js';
|
|
3
|
-
import { TestRailClient } from '../../src/client/testrail.js';
|
|
4
|
-
import { Case } from '../../src/types/testrail.js';
|
|
5
|
-
|
|
6
|
-
describe('update_case tool', () => {
|
|
7
|
-
let mockClient: jest.Mocked<TestRailClient>;
|
|
8
|
-
let updateCaseMock: jest.Mock<(caseId: string, fields: Record<string, any>) => Promise<Case>>;
|
|
9
|
-
|
|
10
|
-
const mockUpdatedCase: Case = {
|
|
11
|
-
id: 123,
|
|
12
|
-
title: 'Updated Title',
|
|
13
|
-
section_id: 1,
|
|
14
|
-
template_id: 1,
|
|
15
|
-
type_id: 1,
|
|
16
|
-
priority_id: 2,
|
|
17
|
-
milestone_id: null,
|
|
18
|
-
refs: null,
|
|
19
|
-
created_by: 1,
|
|
20
|
-
created_on: 1700000000,
|
|
21
|
-
updated_by: 1,
|
|
22
|
-
updated_on: 1700000001,
|
|
23
|
-
estimate: null,
|
|
24
|
-
estimate_forecast: null,
|
|
25
|
-
suite_id: 1,
|
|
26
|
-
display_order: 1,
|
|
27
|
-
is_deleted: 0,
|
|
28
|
-
labels: [],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
updateCaseMock = jest.fn<(caseId: string, fields: Record<string, any>) => Promise<Case>>()
|
|
33
|
-
.mockResolvedValue(mockUpdatedCase);
|
|
34
|
-
|
|
35
|
-
mockClient = {
|
|
36
|
-
updateCase: updateCaseMock
|
|
37
|
-
} as unknown as jest.Mocked<TestRailClient>;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('exports correct tool definition', () => {
|
|
41
|
-
expect(updateCaseTool.name).toBe('update_case');
|
|
42
|
-
expect(updateCaseTool.description).toContain('Update');
|
|
43
|
-
expect(updateCaseTool.parameters).toBeDefined();
|
|
44
|
-
expect(updateCaseTool.parameters.case_id).toBeDefined();
|
|
45
|
-
expect(updateCaseTool.parameters.fields).toBeDefined();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('handler updates case and returns success response', async () => {
|
|
49
|
-
const result = await updateCaseTool.handler(
|
|
50
|
-
{ case_id: '123', fields: { title: 'Updated Title' } },
|
|
51
|
-
mockClient
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
expect(result).toBeDefined();
|
|
55
|
-
expect(result.success).toBe(true);
|
|
56
|
-
expect(result.case_id).toBe(123);
|
|
57
|
-
expect(result.message).toContain('C123');
|
|
58
|
-
expect(mockClient.updateCase).toHaveBeenCalledWith('123', { title: 'Updated Title' });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('strips C prefix from case_id', async () => {
|
|
62
|
-
await updateCaseTool.handler(
|
|
63
|
-
{ case_id: 'C456', fields: { title: 'New Title' } },
|
|
64
|
-
mockClient
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
expect(mockClient.updateCase).toHaveBeenCalledWith('456', { title: 'New Title' });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('handles lowercase c prefix', async () => {
|
|
71
|
-
await updateCaseTool.handler(
|
|
72
|
-
{ case_id: 'c789', fields: { priority_id: 3 } },
|
|
73
|
-
mockClient
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
expect(mockClient.updateCase).toHaveBeenCalledWith('789', { priority_id: 3 });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('passes multiple fields correctly', async () => {
|
|
80
|
-
const fields = {
|
|
81
|
-
title: 'New Title',
|
|
82
|
-
priority_id: 2,
|
|
83
|
-
custom_automation_priority: 1,
|
|
84
|
-
refs: 'JIRA-123',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
await updateCaseTool.handler({ case_id: '123', fields }, mockClient);
|
|
88
|
-
|
|
89
|
-
expect(mockClient.updateCase).toHaveBeenCalledWith('123', fields);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('handler throws error on failure', async () => {
|
|
93
|
-
updateCaseMock.mockRejectedValue(new Error('API Error'));
|
|
94
|
-
|
|
95
|
-
await expect(
|
|
96
|
-
updateCaseTool.handler(
|
|
97
|
-
{ case_id: '123', fields: { title: 'Test' } },
|
|
98
|
-
mockClient
|
|
99
|
-
)
|
|
100
|
-
).rejects.toThrow('API Error');
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { jest, describe, test, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import { updateCasesTool } from '../../src/tools/update_cases.js';
|
|
3
|
-
import { TestRailClient } from '../../src/client/testrail.js';
|
|
4
|
-
import { Case } from '../../src/types/testrail.js';
|
|
5
|
-
|
|
6
|
-
describe('update_cases tool', () => {
|
|
7
|
-
let mockClient: jest.Mocked<TestRailClient>;
|
|
8
|
-
let updateCasesMock: jest.Mock<(caseIds: number[], fields: Record<string, any>) => Promise<Case[]>>;
|
|
9
|
-
|
|
10
|
-
const mockUpdatedCases: Case[] = [
|
|
11
|
-
{
|
|
12
|
-
id: 1, title: 'Test Case 1', section_id: 1, template_id: 1, type_id: 1,
|
|
13
|
-
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
14
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
15
|
-
suite_id: 1, labels: []
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
id: 2, title: 'Test Case 2', section_id: 1, template_id: 1, type_id: 1,
|
|
19
|
-
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
20
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
21
|
-
suite_id: 1, labels: []
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 3, title: 'Test Case 3', section_id: 1, template_id: 1, type_id: 1,
|
|
25
|
-
priority_id: 3, milestone_id: null, refs: null, created_on: 1700000000,
|
|
26
|
-
updated_by: 1, updated_on: 1700000001, estimate: null,
|
|
27
|
-
suite_id: 1, labels: []
|
|
28
|
-
},
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
updateCasesMock = jest.fn<(caseIds: number[], fields: Record<string, any>) => Promise<Case[]>>()
|
|
33
|
-
.mockResolvedValue(mockUpdatedCases);
|
|
34
|
-
|
|
35
|
-
mockClient = {
|
|
36
|
-
updateCases: updateCasesMock
|
|
37
|
-
} as unknown as jest.Mocked<TestRailClient>;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('exports correct tool definition', () => {
|
|
41
|
-
expect(updateCasesTool.name).toBe('update_cases');
|
|
42
|
-
expect(updateCasesTool.description).toContain('Bulk');
|
|
43
|
-
expect(updateCasesTool.parameters).toBeDefined();
|
|
44
|
-
expect(updateCasesTool.parameters.case_ids).toBeDefined();
|
|
45
|
-
expect(updateCasesTool.parameters.fields).toBeDefined();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('handler updates cases and returns success response', async () => {
|
|
49
|
-
const result = await updateCasesTool.handler(
|
|
50
|
-
{ case_ids: [1, 2, 3], fields: { priority_id: 3 } },
|
|
51
|
-
mockClient
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
expect(result).toBeDefined();
|
|
55
|
-
expect(result.success).toBe(true);
|
|
56
|
-
expect(result.updated_count).toBe(3);
|
|
57
|
-
expect(result.case_ids).toEqual([1, 2, 3]);
|
|
58
|
-
expect(result.message).toContain('3 test cases');
|
|
59
|
-
expect(mockClient.updateCases).toHaveBeenCalledWith([1, 2, 3], { priority_id: 3 });
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { jest, describe, test, expect } from '@jest/globals';
|
|
2
|
-
import { Case, CaseField } from '../../src/types/testrail.js';
|
|
3
|
-
import { processCustomFields } from '../../src/utils/mapper.js';
|
|
4
|
-
|
|
5
|
-
describe('processCustomFields', () => {
|
|
6
|
-
|
|
7
|
-
// Mock CaseField array simulating TestRail API response
|
|
8
|
-
const mockCaseFields: CaseField[] = [
|
|
9
|
-
{ id: 1, name: 'is_automated', system_name: 'custom_is_automated', label: 'Is Automated', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
|
|
10
|
-
{ id: 2, name: 'refs', system_name: 'custom_refs', label: 'Defects', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
|
|
11
|
-
{ id: 3, name: 'case_automation_priority', system_name: 'custom_case_automation_priority', label: 'Automation Priority', type_id: 6, template_ids: [], include_all: true, is_active: true, description: null, configs: [{ options: { items: '2, P0\n3, P1\n4, P2\n5, P3\n6, P4' } }] },
|
|
12
|
-
{ id: 4, name: 'preconds', system_name: 'custom_preconds', label: 'Preconditions', type_id: 1, template_ids: [], include_all: true, is_active: true, description: null, configs: [] },
|
|
13
|
-
{ id: 5, name: 'steps', system_name: 'custom_steps', label: 'Steps', type_id: 1, template_ids: [1], include_all: false, is_active: true, description: null, configs: [] },
|
|
14
|
-
{ id: 6, name: 'expected', system_name: 'custom_expected', label: 'Expected', type_id: 1, template_ids: [1], include_all: false, is_active: true, description: null, configs: [] },
|
|
15
|
-
{ id: 7, name: 'steps_separated', system_name: 'custom_steps_separated', label: 'Steps Separated', type_id: 1, template_ids: [2], include_all: false, is_active: true, description: null, configs: [] },
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
test('maps custom fields using field metadata', () => {
|
|
19
|
-
const input: Case = {
|
|
20
|
-
id: 1, title: 'Foo', template_id: 1,
|
|
21
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
22
|
-
custom_is_automated: true,
|
|
23
|
-
custom_refs: 'DEF-123',
|
|
24
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
25
|
-
is_deleted: 0, refs: null, labels: [],
|
|
26
|
-
} as unknown as Case;
|
|
27
|
-
|
|
28
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
29
|
-
expect(result.is_automated).toBe(true);
|
|
30
|
-
expect(result.defects).toBe('DEF-123');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('maps fields using value mapping from config', () => {
|
|
34
|
-
const input: Case = {
|
|
35
|
-
id: 1, title: 'Foo', template_id: 1,
|
|
36
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
37
|
-
custom_case_automation_priority: 2,
|
|
38
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
39
|
-
is_deleted: 0, refs: null, labels: [],
|
|
40
|
-
} as unknown as Case;
|
|
41
|
-
|
|
42
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
43
|
-
expect(result.automation_priority).toBe('P0');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('includes only template-specific fields for template 1', () => {
|
|
47
|
-
const input: Case = {
|
|
48
|
-
id: 1, title: 'Foo', template_id: 1,
|
|
49
|
-
custom_steps: 'Step 1...',
|
|
50
|
-
custom_expected: 'Expectation...',
|
|
51
|
-
custom_steps_separated: [{ content: 'Should not appear' }], // Template 2 field
|
|
52
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
53
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
54
|
-
is_deleted: 0, refs: null, labels: [],
|
|
55
|
-
} as unknown as Case;
|
|
56
|
-
|
|
57
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
58
|
-
expect(result.steps).toBe('Step 1...');
|
|
59
|
-
expect(result.expected).toBe('Expectation...');
|
|
60
|
-
expect(result.steps_separated).toBeUndefined(); // Not included for template 1
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('includes only template-specific fields for template 2', () => {
|
|
64
|
-
const input: Case = {
|
|
65
|
-
id: 1, title: 'Foo', template_id: 2,
|
|
66
|
-
custom_steps: 'Should not appear', // Template 1 field
|
|
67
|
-
custom_steps_separated: [{ content: 'Step 1' }],
|
|
68
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
69
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
70
|
-
is_deleted: 0, refs: null, labels: [],
|
|
71
|
-
} as unknown as Case;
|
|
72
|
-
|
|
73
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
74
|
-
expect(result.steps_separated).toEqual([{ content: 'Step 1' }]);
|
|
75
|
-
expect(result.steps).toBeUndefined(); // Not included for template 2
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('keeps unknown fields as-is when no mapping found', () => {
|
|
79
|
-
const input: Case = {
|
|
80
|
-
id: 1, title: 'Foo', template_id: 999,
|
|
81
|
-
custom_unknown_field: 'some value',
|
|
82
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
83
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
84
|
-
is_deleted: 0, refs: null, labels: [],
|
|
85
|
-
} as unknown as Case;
|
|
86
|
-
|
|
87
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
88
|
-
expect(result.custom_unknown_field).toBe('some value'); // Kept as-is with original key
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('throws error when testCase is null', () => {
|
|
92
|
-
expect(() => processCustomFields(null as any, mockCaseFields)).toThrow('Test case is undefined or null');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('throws error when testCase is undefined', () => {
|
|
96
|
-
expect(() => processCustomFields(undefined as any, mockCaseFields)).toThrow('Test case is undefined or null');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('returns original value when dropdown option is not found', () => {
|
|
100
|
-
const input: Case = {
|
|
101
|
-
id: 1, title: 'Foo', template_id: 1,
|
|
102
|
-
section_id: 0, type_id: 0, priority_id: 0,
|
|
103
|
-
custom_case_automation_priority: 999, // Unknown option ID
|
|
104
|
-
display_order: 1, suite_id: 1, created_on: 1, updated_on: 1,
|
|
105
|
-
is_deleted: 0, refs: null, labels: [],
|
|
106
|
-
} as unknown as Case;
|
|
107
|
-
|
|
108
|
-
const result = processCustomFields(input, mockCaseFields);
|
|
109
|
-
expect(result.automation_priority).toBe(999); // Falls back to original value
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from '@jest/globals';
|
|
2
|
-
import { sanitizeValue, removeNullish } from '../../src/utils/sanitizer.js';
|
|
3
|
-
|
|
4
|
-
describe('sanitizeValue', () => {
|
|
5
|
-
test('strips style attributes from string values', () => {
|
|
6
|
-
const input = '<p style="color: red; font-size: 14px;">Hello</p>';
|
|
7
|
-
expect(sanitizeValue(input)).toBe('<p>Hello</p>');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('strips multiple style attributes', () => {
|
|
11
|
-
const input = '<div style="margin: 10px;"><span style="font-weight: bold;">Text</span></div>';
|
|
12
|
-
expect(sanitizeValue(input)).toBe('<div><span>Text</span></div>');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('handles single quotes in style', () => {
|
|
16
|
-
const input = "<p style='color: blue;'>Text</p>";
|
|
17
|
-
expect(sanitizeValue(input)).toBe('<p>Text</p>');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('preserves other attributes', () => {
|
|
21
|
-
const input = '<a href="http://example.com" style="color: blue;" class="link">Link</a>';
|
|
22
|
-
expect(sanitizeValue(input)).toBe('<a href="http://example.com" class="link">Link</a>');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('returns unchanged text without styles', () => {
|
|
26
|
-
const input = '<p>No styles here</p>';
|
|
27
|
-
expect(sanitizeValue(input)).toBe('<p>No styles here</p>');
|
|
28
|
-
});
|
|
29
|
-
test('sanitizes string values', () => {
|
|
30
|
-
const input = '<p style="color: red;">Hello</p>';
|
|
31
|
-
expect(sanitizeValue(input)).toBe('<p>Hello</p>');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('sanitizes nested objects', () => {
|
|
35
|
-
const input = {
|
|
36
|
-
content: '<div style="margin: 10px;">Step 1</div>',
|
|
37
|
-
expected: '<p style="color: green;">Result</p>'
|
|
38
|
-
};
|
|
39
|
-
const result = sanitizeValue(input);
|
|
40
|
-
expect(result.content).toBe('<div>Step 1</div>');
|
|
41
|
-
expect(result.expected).toBe('<p>Result</p>');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('sanitizes arrays', () => {
|
|
45
|
-
const input = [
|
|
46
|
-
{ content: '<span style="font-size: 12px;">Item 1</span>' },
|
|
47
|
-
{ content: '<span style="font-size: 14px;">Item 2</span>' }
|
|
48
|
-
];
|
|
49
|
-
const result = sanitizeValue(input);
|
|
50
|
-
expect(result[0].content).toBe('<span>Item 1</span>');
|
|
51
|
-
expect(result[1].content).toBe('<span>Item 2</span>');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('preserves non-string values', () => {
|
|
55
|
-
expect(sanitizeValue(42)).toBe(42);
|
|
56
|
-
expect(sanitizeValue(true)).toBe(true);
|
|
57
|
-
expect(sanitizeValue(null)).toBe(null);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('removeNullish', () => {
|
|
62
|
-
test('removes null values from object', () => {
|
|
63
|
-
const input = { a: 1, b: null, c: 3 };
|
|
64
|
-
expect(removeNullish(input)).toEqual({ a: 1, c: 3 });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('removes undefined values from object', () => {
|
|
68
|
-
const input = { a: 1, b: undefined, c: 3 };
|
|
69
|
-
expect(removeNullish(input)).toEqual({ a: 1, c: 3 });
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('handles nested objects', () => {
|
|
73
|
-
const input = {
|
|
74
|
-
name: 'test',
|
|
75
|
-
metadata: { id: 1, value: null, active: true },
|
|
76
|
-
count: null
|
|
77
|
-
};
|
|
78
|
-
expect(removeNullish(input)).toEqual({
|
|
79
|
-
name: 'test',
|
|
80
|
-
metadata: { id: 1, active: true }
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('filters null items from arrays', () => {
|
|
85
|
-
const input = [1, null, 3, undefined, 5];
|
|
86
|
-
expect(removeNullish(input)).toEqual([1, 3, 5]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('cleans nested objects in arrays', () => {
|
|
90
|
-
const input = [
|
|
91
|
-
{ id: 1, name: 'item1', value: null },
|
|
92
|
-
{ id: 2, name: null, value: 'test' }
|
|
93
|
-
];
|
|
94
|
-
expect(removeNullish(input)).toEqual([
|
|
95
|
-
{ id: 1, name: 'item1' },
|
|
96
|
-
{ id: 2, value: 'test' }
|
|
97
|
-
]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('handles deeply nested structures', () => {
|
|
101
|
-
const input = {
|
|
102
|
-
projects: [
|
|
103
|
-
{
|
|
104
|
-
id: 1,
|
|
105
|
-
name: 'Project A',
|
|
106
|
-
metadata: { created: null, updated: '2024-01-01' },
|
|
107
|
-
tags: [null, 'important', undefined, 'urgent']
|
|
108
|
-
},
|
|
109
|
-
null
|
|
110
|
-
],
|
|
111
|
-
count: undefined
|
|
112
|
-
};
|
|
113
|
-
expect(removeNullish(input)).toEqual({
|
|
114
|
-
projects: [
|
|
115
|
-
{
|
|
116
|
-
id: 1,
|
|
117
|
-
name: 'Project A',
|
|
118
|
-
metadata: { updated: '2024-01-01' },
|
|
119
|
-
tags: ['important', 'urgent']
|
|
120
|
-
}
|
|
121
|
-
]
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('preserves primitive values', () => {
|
|
126
|
-
expect(removeNullish(42)).toBe(42);
|
|
127
|
-
expect(removeNullish('test')).toBe('test');
|
|
128
|
-
expect(removeNullish(true)).toBe(true);
|
|
129
|
-
expect(removeNullish(0)).toBe(0);
|
|
130
|
-
expect(removeNullish('')).toBe('');
|
|
131
|
-
expect(removeNullish(false)).toBe(false);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('returns null/undefined as-is for root values', () => {
|
|
135
|
-
expect(removeNullish(null)).toBe(null);
|
|
136
|
-
expect(removeNullish(undefined)).toBe(undefined);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('handles empty objects and arrays', () => {
|
|
140
|
-
expect(removeNullish({})).toEqual({});
|
|
141
|
-
expect(removeNullish([])).toEqual([]);
|
|
142
|
-
});
|
|
143
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"types": [
|
|
13
|
-
"node",
|
|
14
|
-
"jest"
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
"include": [
|
|
18
|
-
"src/**/*"
|
|
19
|
-
],
|
|
20
|
-
"exclude": [
|
|
21
|
-
"node_modules",
|
|
22
|
-
"**/*.test.ts"
|
|
23
|
-
]
|
|
24
|
-
}
|