@uarlouski/testrail-mcp-server 1.3.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.
Files changed (45) hide show
  1. package/dist/client/testrail.js +167 -0
  2. package/{src/index.ts → dist/index.js} +25 -35
  3. package/{src/tools/add_attachment_to_run.ts → dist/tools/add_attachment_to_run.js} +8 -18
  4. package/{src/tools/add_results.ts → dist/tools/add_results.js} +3 -7
  5. package/{src/tools/add_run.ts → dist/tools/add_run.js} +3 -8
  6. package/{src/tools/create_case.ts → dist/tools/create_case.js} +1 -7
  7. package/{src/tools/get_case.ts → dist/tools/get_case.js} +2 -11
  8. package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
  9. package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +5 -19
  10. package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +1 -6
  11. package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +1 -6
  12. package/{src/tools/get_statuses.ts → dist/tools/get_statuses.js} +2 -7
  13. package/{src/tools/get_templates.ts → dist/tools/get_templates.js} +1 -6
  14. package/{src/tools/get_tests.ts → dist/tools/get_tests.js} +5 -10
  15. package/{src/tools/update_case.ts → dist/tools/update_case.js} +1 -7
  16. package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +1 -6
  17. package/dist/types/custom.js +1 -0
  18. package/{src/types/testrail.ts → dist/types/testrail.js} +2 -42
  19. package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
  20. package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
  21. package/package.json +8 -2
  22. package/.env.example +0 -3
  23. package/.github/workflows/ci.yml +0 -46
  24. package/.github/workflows/publish.yml +0 -35
  25. package/CHANGELOG.md +0 -38
  26. package/jest.config.js +0 -24
  27. package/src/client/testrail.ts +0 -227
  28. package/src/types/custom.ts +0 -19
  29. package/test/client/testrail.test.ts +0 -545
  30. package/test/tools/add_results.test.ts +0 -94
  31. package/test/tools/add_run.test.ts +0 -83
  32. package/test/tools/create_case.test.ts +0 -101
  33. package/test/tools/get_case.test.ts +0 -185
  34. package/test/tools/get_case_fields.test.ts +0 -132
  35. package/test/tools/get_cases.test.ts +0 -104
  36. package/test/tools/get_projects.test.ts +0 -38
  37. package/test/tools/get_sections.test.ts +0 -39
  38. package/test/tools/get_statuses.test.ts +0 -56
  39. package/test/tools/get_templates.test.ts +0 -38
  40. package/test/tools/get_tests.test.ts +0 -47
  41. package/test/tools/update_case.test.ts +0 -102
  42. package/test/tools/update_cases.test.ts +0 -89
  43. package/test/utils/mapper.test.ts +0 -112
  44. package/test/utils/sanitizer.test.ts +0 -143
  45. package/tsconfig.json +0 -24
@@ -1,56 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { getStatusesTool } from '../../src/tools/get_statuses.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Status } from '../../src/types/testrail.js';
5
-
6
- describe('get_statuses tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let getStatusesMock: jest.Mock<() => Promise<Status[]>>;
9
-
10
- const mockStatuses: Status[] = [
11
- { id: 1, name: 'passed', label: 'Passed' },
12
- { id: 2, name: 'blocked', label: 'Blocked' },
13
- { id: 3, name: 'untested', label: 'Untested' },
14
- { id: 4, name: 'retest', label: 'Retest' },
15
- { id: 5, name: 'failed', label: 'Failed' },
16
- ];
17
-
18
- beforeEach(() => {
19
- getStatusesMock = jest.fn<() => Promise<Status[]>>().mockResolvedValue(mockStatuses);
20
-
21
- mockClient = {
22
- getStatuses: getStatusesMock
23
- } as unknown as jest.Mocked<TestRailClient>;
24
- });
25
-
26
- test('exports correct tool definition', () => {
27
- expect(getStatusesTool.name).toBe('get_statuses');
28
- expect(getStatusesTool.description).toContain('statuses');
29
- expect(getStatusesTool.parameters).toBeDefined();
30
- });
31
-
32
- test('handler fetches and returns statuses', async () => {
33
- const result = await getStatusesTool.handler({}, mockClient);
34
-
35
- expect(result).toBeDefined();
36
- expect(result.statuses).toBeDefined();
37
- expect(result.statuses).toHaveLength(5);
38
- expect(mockClient.getStatuses).toHaveBeenCalled();
39
- });
40
-
41
- test('returns correct status structure', async () => {
42
- const result = await getStatusesTool.handler({}, mockClient);
43
-
44
- expect(result.statuses[0]).toEqual({
45
- id: 1,
46
- name: 'passed',
47
- label: 'Passed'
48
- });
49
- });
50
-
51
- test('handler returns error on failure', async () => {
52
- getStatusesMock.mockRejectedValue(new Error('API Error'));
53
-
54
- await expect(getStatusesTool.handler({}, mockClient)).rejects.toThrow('API Error');
55
- });
56
- });
@@ -1,38 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { getTemplatesTool } from '../../src/tools/get_templates.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Template } from '../../src/types/testrail.js';
5
-
6
- describe('get_templates tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let getTemplatesMock: jest.Mock<(projectId: string) => Promise<Template[]>>;
9
-
10
- const mockTemplates: Template[] = [
11
- { id: 1, name: 'Test Case (Text)', is_default: false },
12
- { id: 2, name: 'Test Case (Steps)', is_default: true },
13
- { id: 3, name: 'Exploratory Session', is_default: false },
14
- ];
15
-
16
- beforeEach(() => {
17
- getTemplatesMock = jest.fn<(projectId: string) => Promise<Template[]>>().mockResolvedValue(mockTemplates);
18
-
19
- mockClient = {
20
- getTemplates: getTemplatesMock
21
- } as unknown as jest.Mocked<TestRailClient>;
22
- });
23
-
24
- test('exports correct tool definition', () => {
25
- expect(getTemplatesTool.name).toBe('get_templates');
26
- expect(getTemplatesTool.description).toContain('template');
27
- expect(getTemplatesTool.parameters).toBeDefined();
28
- expect(getTemplatesTool.parameters.project_id).toBeDefined();
29
- });
30
-
31
- test('handler fetches and returns templates', async () => {
32
- const result = await getTemplatesTool.handler({ project_id: '1' }, mockClient);
33
-
34
- expect(result).toBeDefined();
35
- expect(result.templates).toHaveLength(3);
36
- expect(mockClient.getTemplates).toHaveBeenCalledWith('1');
37
- });
38
- });
@@ -1,47 +0,0 @@
1
- import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
- import { getTestsTool } from '../../src/tools/get_tests.js';
3
- import { TestRailClient } from '../../src/client/testrail.js';
4
- import { Test } from '../../src/types/testrail.js';
5
-
6
- describe('get_tests tool', () => {
7
- let mockClient: jest.Mocked<TestRailClient>;
8
- let getTestsMock: jest.Mock<(runId: number, statusId?: number[]) => Promise<Test[]>>;
9
-
10
- const mockTests: Test[] = [
11
- { id: 1, case_id: 101, status_id: 1, title: 'Test 1', run_id: 1 },
12
- { id: 2, case_id: 102, status_id: 2, title: 'Test 2', run_id: 1 },
13
- ];
14
-
15
- beforeEach(() => {
16
- getTestsMock = jest.fn<(runId: number, statusId?: number[]) => Promise<Test[]>>().mockResolvedValue(mockTests);
17
-
18
- mockClient = {
19
- getTests: getTestsMock
20
- } as unknown as jest.Mocked<TestRailClient>;
21
- });
22
-
23
- test('exports correct tool definition', () => {
24
- expect(getTestsTool.name).toBe('get_tests');
25
- expect(getTestsTool.description).toBeDefined();
26
- expect(getTestsTool.parameters).toBeDefined();
27
- });
28
-
29
- test('handler fetches and returns tests', async () => {
30
- const result = await getTestsTool.handler({ run_id: 1 }, mockClient);
31
-
32
- expect(result).toBeDefined();
33
- expect(result.tests).toBeDefined();
34
- expect(result.tests).toHaveLength(2);
35
- expect(mockClient.getTests).toHaveBeenCalledWith(1, undefined);
36
- });
37
-
38
- test('handler passes status_id filter', async () => {
39
- const result = await getTestsTool.handler({ run_id: 1, status_id: [1, 5] }, mockClient);
40
- expect(mockClient.getTests).toHaveBeenCalledWith(1, [1, 5]);
41
- });
42
-
43
- test('handler returns error on failure', async () => {
44
- getTestsMock.mockRejectedValue(new Error('API Error'));
45
- await expect(getTestsTool.handler({ run_id: 1 }, mockClient)).rejects.toThrow('API Error');
46
- });
47
- });
@@ -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,89 +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
- getCase: jest.fn(),
38
- } as unknown as jest.Mocked<TestRailClient>;
39
-
40
- // Default mock for getCase to avoid failures in unrelated tests
41
- (mockClient.getCase as jest.Mock<any>).mockResolvedValue({ suite_id: 1 });
42
- });
43
-
44
- test('exports correct tool definition', () => {
45
- expect(updateCasesTool.name).toBe('update_cases');
46
- expect(updateCasesTool.description).toContain('Bulk');
47
- expect(updateCasesTool.parameters).toBeDefined();
48
- expect(updateCasesTool.parameters.case_ids).toBeDefined();
49
- expect(updateCasesTool.parameters.fields).toBeDefined();
50
- });
51
-
52
- test('handler updates cases and returns success response', async () => {
53
- const result = await updateCasesTool.handler(
54
- { case_ids: [1, 2, 3], fields: { priority_id: 3 } },
55
- mockClient
56
- );
57
-
58
- expect(result).toBeDefined();
59
- expect(result.success).toBe(true);
60
- expect(result.updated_count).toBe(3);
61
- expect(result.case_ids).toEqual([1, 2, 3]);
62
- expect(result.message).toContain('3 test cases');
63
- });
64
-
65
- test('passes case_ids and fields correctly', async () => {
66
- const fields = { priority_id: 2, type_id: 1 };
67
- const mockCase = { suite_id: 5 } as Case;
68
- (mockClient.getCase as jest.Mock<any>).mockResolvedValue(mockCase);
69
-
70
- await updateCasesTool.handler(
71
- { case_ids: [10, 20, 30], fields },
72
- mockClient
73
- );
74
-
75
- expect(mockClient.getCase).toHaveBeenCalledWith('10');
76
- expect(mockClient.updateCases).toHaveBeenCalledWith(5, [10, 20, 30], fields);
77
- });
78
-
79
- test('handler throws error on failure', async () => {
80
- updateCasesMock.mockRejectedValue(new Error('API Error'));
81
-
82
- await expect(
83
- updateCasesTool.handler(
84
- { case_ids: [1, 2], fields: { priority_id: 2 } },
85
- mockClient
86
- )
87
- ).rejects.toThrow('API Error');
88
- });
89
- });
@@ -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
- }