@memberjunction/ng-dashboards 5.25.0 → 5.27.0
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/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts +96 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js +710 -0
- package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +52 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.js +356 -0
- package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts +52 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.js +306 -0
- package/dist/AI/components/analytics/analytics-filter-bar.component.js.map +1 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +81 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +744 -0
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts +61 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.js +490 -0
- package/dist/AI/components/analytics/error-analysis/error-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts +77 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.js +673 -0
- package/dist/AI/components/analytics/executive-summary/executive-summary.component.js.map +1 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts +65 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.js +537 -0
- package/dist/AI/components/analytics/model-performance/model-performance.component.js.map +1 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts +131 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js +1030 -0
- package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js.map +1 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts +78 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts.map +1 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js +569 -0
- package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js.map +1 -0
- package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
- package/dist/AI/components/execution-monitoring.component.js +4 -14
- package/dist/AI/components/execution-monitoring.component.js.map +1 -1
- package/dist/AI/components/overview/ai-overview-hub.component.d.ts +58 -0
- package/dist/AI/components/overview/ai-overview-hub.component.d.ts.map +1 -0
- package/dist/AI/components/overview/ai-overview-hub.component.js +315 -0
- package/dist/AI/components/overview/ai-overview-hub.component.js.map +1 -0
- package/dist/AI/components/prompts/prompt-management.component.js +1 -1
- package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
- package/dist/AI/index.d.ts +11 -0
- package/dist/AI/index.d.ts.map +1 -1
- package/dist/AI/index.js +13 -0
- package/dist/AI/index.js.map +1 -1
- package/dist/AI/interfaces/analytics-preferences.interface.d.ts +50 -0
- package/dist/AI/interfaces/analytics-preferences.interface.d.ts.map +1 -0
- package/dist/AI/interfaces/analytics-preferences.interface.js +9 -0
- package/dist/AI/interfaces/analytics-preferences.interface.js.map +1 -0
- package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
- package/dist/Home/home-dashboard.component.js +2 -2
- package/dist/MCP/index.d.ts +1 -0
- package/dist/MCP/index.d.ts.map +1 -1
- package/dist/MCP/index.js +2 -0
- package/dist/MCP/index.js.map +1 -1
- package/dist/MCP/mcp-dashboard.component.d.ts +1 -0
- package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
- package/dist/MCP/mcp-dashboard.component.js +5 -4
- package/dist/MCP/mcp-dashboard.component.js.map +1 -1
- package/dist/MCP/mcp-resource.component.d.ts +6 -5
- package/dist/MCP/mcp-resource.component.d.ts.map +1 -1
- package/dist/MCP/mcp-resource.component.js +7 -8
- package/dist/MCP/mcp-resource.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +27 -17
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +66 -3
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +1 -1
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -1
- package/dist/public-api.js.map +1 -1
- package/package.json +49 -48
- package/dist/__tests__/analytics-resource.test.d.ts +0 -2
- package/dist/__tests__/analytics-resource.test.d.ts.map +0 -1
- package/dist/__tests__/analytics-resource.test.js +0 -181
- package/dist/__tests__/analytics-resource.test.js.map +0 -1
- package/dist/__tests__/dashboards.test.d.ts +0 -2
- package/dist/__tests__/dashboards.test.d.ts.map +0 -1
- package/dist/__tests__/dashboards.test.js +0 -40
- package/dist/__tests__/dashboards.test.js.map +0 -1
- package/dist/__tests__/integration-data-service.test.d.ts +0 -2
- package/dist/__tests__/integration-data-service.test.d.ts.map +0 -1
- package/dist/__tests__/integration-data-service.test.js +0 -132
- package/dist/__tests__/integration-data-service.test.js.map +0 -1
- package/dist/__tests__/mapping-validation.test.d.ts +0 -2
- package/dist/__tests__/mapping-validation.test.d.ts.map +0 -1
- package/dist/__tests__/mapping-validation.test.js +0 -170
- package/dist/__tests__/mapping-validation.test.js.map +0 -1
- package/dist/__tests__/scheduling.test.d.ts +0 -2
- package/dist/__tests__/scheduling.test.d.ts.map +0 -1
- package/dist/__tests__/scheduling.test.js +0 -205
- package/dist/__tests__/scheduling.test.js.map +0 -1
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for analytics resource component logic:
|
|
3
|
-
* - AN-1: Drill-down Open Record
|
|
4
|
-
* - AN-3: CSV Export
|
|
5
|
-
* - SR-6: Preference persistence
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
8
|
-
// ── Test the pure-logic utility functions extracted from the component ──
|
|
9
|
-
describe('Analytics Resource — CSV Export (AN-3)', () => {
|
|
10
|
-
// Test the CSV generation logic independently
|
|
11
|
-
function buildCSV(columns, data) {
|
|
12
|
-
const escape = (v) => {
|
|
13
|
-
const s = String(v ?? '');
|
|
14
|
-
return s.includes(',') || s.includes('"') || s.includes('\n')
|
|
15
|
-
? `"${s.replace(/"/g, '""')}"` : s;
|
|
16
|
-
};
|
|
17
|
-
const header = columns.map(escape).join(',');
|
|
18
|
-
const rows = data.map(row => columns.map(c => escape(row[c])).join(','));
|
|
19
|
-
return [header, ...rows].join('\n');
|
|
20
|
-
}
|
|
21
|
-
it('should generate valid CSV header', () => {
|
|
22
|
-
const csv = buildCSV(['Name', 'Count'], []);
|
|
23
|
-
expect(csv).toBe('Name,Count');
|
|
24
|
-
});
|
|
25
|
-
it('should generate CSV with data rows', () => {
|
|
26
|
-
const csv = buildCSV(['Name', 'Score'], [
|
|
27
|
-
{ Name: 'Tag A', Score: 42 },
|
|
28
|
-
{ Name: 'Tag B', Score: 18 },
|
|
29
|
-
]);
|
|
30
|
-
const lines = csv.split('\n');
|
|
31
|
-
expect(lines).toHaveLength(3);
|
|
32
|
-
expect(lines[0]).toBe('Name,Score');
|
|
33
|
-
expect(lines[1]).toBe('Tag A,42');
|
|
34
|
-
expect(lines[2]).toBe('Tag B,18');
|
|
35
|
-
});
|
|
36
|
-
it('should escape values containing commas', () => {
|
|
37
|
-
const csv = buildCSV(['Name'], [{ Name: 'Doe, John' }]);
|
|
38
|
-
expect(csv).toContain('"Doe, John"');
|
|
39
|
-
});
|
|
40
|
-
it('should escape values containing quotes', () => {
|
|
41
|
-
const csv = buildCSV(['Name'], [{ Name: 'He said "hello"' }]);
|
|
42
|
-
expect(csv).toContain('"He said ""hello"""');
|
|
43
|
-
});
|
|
44
|
-
it('should handle null values', () => {
|
|
45
|
-
const csv = buildCSV(['Name', 'Value'], [{ Name: 'Test', Value: null }]);
|
|
46
|
-
const lines = csv.split('\n');
|
|
47
|
-
expect(lines[1]).toBe('Test,');
|
|
48
|
-
});
|
|
49
|
-
it('should not include hidden _RecordID/_EntityName columns', () => {
|
|
50
|
-
// Columns array should NOT include _RecordID/_EntityName
|
|
51
|
-
const columns = ['Tag', 'Usage Count'];
|
|
52
|
-
const data = [
|
|
53
|
-
{ Tag: 'CRM', 'Usage Count': 42, _RecordID: 'abc-123', _EntityName: 'MJ: Tags' },
|
|
54
|
-
];
|
|
55
|
-
const csv = buildCSV(columns, data);
|
|
56
|
-
expect(csv).not.toContain('_RecordID');
|
|
57
|
-
expect(csv).not.toContain('abc-123');
|
|
58
|
-
expect(csv).toContain('CRM');
|
|
59
|
-
expect(csv).toContain('42');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe('Analytics Resource — Drill-Down Actions (AN-1)', () => {
|
|
63
|
-
it('should include _RecordID and _EntityName in drill-down data for navigable rows', () => {
|
|
64
|
-
// Simulating what loadDrillDownData produces for kpi-totalTags
|
|
65
|
-
const rawTag = { ID: 'tag-1', Name: 'CRM', DisplayName: 'CRM', Description: 'Customer RM', Parent: 'Root' };
|
|
66
|
-
const drillDownRow = {
|
|
67
|
-
'Name': String(rawTag.Name),
|
|
68
|
-
'Display Name': String(rawTag.DisplayName),
|
|
69
|
-
'Description': String(rawTag.Description),
|
|
70
|
-
'Parent': String(rawTag.Parent),
|
|
71
|
-
'_RecordID': String(rawTag.ID),
|
|
72
|
-
'_EntityName': 'MJ: Tags',
|
|
73
|
-
};
|
|
74
|
-
expect(drillDownRow['_RecordID']).toBe('tag-1');
|
|
75
|
-
expect(drillDownRow['_EntityName']).toBe('MJ: Tags');
|
|
76
|
-
});
|
|
77
|
-
it('should not include _RecordID for aggregate-only drill-downs', () => {
|
|
78
|
-
// contentCoverage and qualityScore don't have individual records
|
|
79
|
-
const coverageRow = {
|
|
80
|
-
'Content Type': 'Articles',
|
|
81
|
-
'Total Items': 100,
|
|
82
|
-
'Tagged Items': 85,
|
|
83
|
-
'Coverage %': '85%',
|
|
84
|
-
};
|
|
85
|
-
expect(coverageRow).not.toHaveProperty('_RecordID');
|
|
86
|
-
expect(coverageRow).not.toHaveProperty('_EntityName');
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('Analytics Resource — Cost Aggregation (D1)', () => {
|
|
90
|
-
// Re-implement the static SumField method from the component for testing
|
|
91
|
-
function sumField(records, fieldName) {
|
|
92
|
-
return records.reduce((sum, r) => sum + Number(r[fieldName] || 0), 0);
|
|
93
|
-
}
|
|
94
|
-
const mockDetails = [
|
|
95
|
-
{ ContentProcessRunID: 'run-1', TotalTokensUsed: 1500, TotalCost: 0.0045 },
|
|
96
|
-
{ ContentProcessRunID: 'run-1', TotalTokensUsed: 2500, TotalCost: 0.0075 },
|
|
97
|
-
{ ContentProcessRunID: 'run-2', TotalTokensUsed: 3000, TotalCost: 0.009 },
|
|
98
|
-
];
|
|
99
|
-
it('should sum total tokens across all detail records', () => {
|
|
100
|
-
const totalTokens = sumField(mockDetails, 'TotalTokensUsed');
|
|
101
|
-
expect(totalTokens).toBe(7000);
|
|
102
|
-
});
|
|
103
|
-
it('should sum total cost across all detail records', () => {
|
|
104
|
-
const totalCost = sumField(mockDetails, 'TotalCost');
|
|
105
|
-
expect(totalCost).toBeCloseTo(0.021, 4);
|
|
106
|
-
});
|
|
107
|
-
it('should calculate average cost per run', () => {
|
|
108
|
-
const totalCost = sumField(mockDetails, 'TotalCost');
|
|
109
|
-
const runCount = 2; // Two unique runs
|
|
110
|
-
const avgCostPerRun = runCount > 0 ? totalCost / runCount : 0;
|
|
111
|
-
expect(avgCostPerRun).toBeCloseTo(0.0105, 4);
|
|
112
|
-
});
|
|
113
|
-
it('should handle empty records gracefully', () => {
|
|
114
|
-
const totalTokens = sumField([], 'TotalTokensUsed');
|
|
115
|
-
const totalCost = sumField([], 'TotalCost');
|
|
116
|
-
expect(totalTokens).toBe(0);
|
|
117
|
-
expect(totalCost).toBe(0);
|
|
118
|
-
});
|
|
119
|
-
it('should handle records with missing/null fields', () => {
|
|
120
|
-
const records = [
|
|
121
|
-
{ ContentProcessRunID: 'run-1', TotalTokensUsed: null, TotalCost: undefined },
|
|
122
|
-
{ ContentProcessRunID: 'run-2' },
|
|
123
|
-
];
|
|
124
|
-
expect(sumField(records, 'TotalTokensUsed')).toBe(0);
|
|
125
|
-
expect(sumField(records, 'TotalCost')).toBe(0);
|
|
126
|
-
});
|
|
127
|
-
it('should filter details by run ID correctly', () => {
|
|
128
|
-
const filteredRunIds = new Set(['run-1']);
|
|
129
|
-
const filteredDetails = mockDetails.filter(d => filteredRunIds.has(String(d['ContentProcessRunID'] || '')));
|
|
130
|
-
const tokens = sumField(filteredDetails, 'TotalTokensUsed');
|
|
131
|
-
expect(tokens).toBe(4000); // 1500 + 2500
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
describe('Analytics Resource — Preference Persistence (SR-6)', () => {
|
|
135
|
-
let mockStorage;
|
|
136
|
-
beforeEach(() => {
|
|
137
|
-
mockStorage = {};
|
|
138
|
-
vi.stubGlobal('localStorage', {
|
|
139
|
-
getItem: vi.fn((key) => mockStorage[key] ?? null),
|
|
140
|
-
setItem: vi.fn((key, value) => { mockStorage[key] = value; }),
|
|
141
|
-
removeItem: vi.fn((key) => { delete mockStorage[key]; }),
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
afterEach(() => {
|
|
145
|
-
vi.unstubAllGlobals();
|
|
146
|
-
});
|
|
147
|
-
it('should persist preferences to localStorage', () => {
|
|
148
|
-
const prefs = {
|
|
149
|
-
ActiveTab: 'tags',
|
|
150
|
-
ActiveDateRange: '90D',
|
|
151
|
-
EntityFilter: 'Contacts',
|
|
152
|
-
};
|
|
153
|
-
localStorage.setItem('KH_AnalyticsPreferences', JSON.stringify(prefs));
|
|
154
|
-
const stored = JSON.parse(localStorage.getItem('KH_AnalyticsPreferences'));
|
|
155
|
-
expect(stored.ActiveTab).toBe('tags');
|
|
156
|
-
expect(stored.ActiveDateRange).toBe('90D');
|
|
157
|
-
expect(stored.EntityFilter).toBe('Contacts');
|
|
158
|
-
});
|
|
159
|
-
it('should load preferences from localStorage', () => {
|
|
160
|
-
const prefs = { ActiveTab: 'pipeline', ActiveDateRange: '7D', EntityFilter: 'All Entities' };
|
|
161
|
-
mockStorage['KH_AnalyticsPreferences'] = JSON.stringify(prefs);
|
|
162
|
-
const raw = localStorage.getItem('KH_AnalyticsPreferences');
|
|
163
|
-
expect(raw).not.toBeNull();
|
|
164
|
-
const parsed = JSON.parse(raw);
|
|
165
|
-
expect(parsed.ActiveTab).toBe('pipeline');
|
|
166
|
-
});
|
|
167
|
-
it('should handle missing localStorage gracefully', () => {
|
|
168
|
-
const raw = localStorage.getItem('KH_AnalyticsPreferences');
|
|
169
|
-
expect(raw).toBeNull();
|
|
170
|
-
// Component should use defaults when no stored prefs
|
|
171
|
-
});
|
|
172
|
-
it('should store search preferences separately', () => {
|
|
173
|
-
const searchPrefs = { ShowFilters: true, MinScoreThreshold: 0.5 };
|
|
174
|
-
localStorage.setItem('KH_SearchPreferences', JSON.stringify(searchPrefs));
|
|
175
|
-
const analyticsPrefs = localStorage.getItem('KH_AnalyticsPreferences');
|
|
176
|
-
expect(analyticsPrefs).toBeNull(); // Should not cross-pollinate
|
|
177
|
-
const searchStored = JSON.parse(localStorage.getItem('KH_SearchPreferences'));
|
|
178
|
-
expect(searchStored.MinScoreThreshold).toBe(0.5);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
//# sourceMappingURL=analytics-resource.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics-resource.test.js","sourceRoot":"","sources":["../../src/__tests__/analytics-resource.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tests for analytics resource component logic:\n * - AN-1: Drill-down Open Record\n * - AN-3: CSV Export\n * - SR-6: Preference persistence\n */\nimport { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';\n\n// ── Test the pure-logic utility functions extracted from the component ──\n\ndescribe('Analytics Resource — CSV Export (AN-3)', () => {\n // Test the CSV generation logic independently\n function buildCSV(\n columns: string[],\n data: Record<string, string | number | null>[],\n ): string {\n const escape = (v: string | number | null): string => {\n const s = String(v ?? '');\n return s.includes(',') || s.includes('\"') || s.includes('\\n')\n ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n const header = columns.map(escape).join(',');\n const rows = data.map(row => columns.map(c => escape(row[c])).join(','));\n return [header, ...rows].join('\\n');\n }\n\n it('should generate valid CSV header', () => {\n const csv = buildCSV(['Name', 'Count'], []);\n expect(csv).toBe('Name,Count');\n });\n\n it('should generate CSV with data rows', () => {\n const csv = buildCSV(\n ['Name', 'Score'],\n [\n { Name: 'Tag A', Score: 42 },\n { Name: 'Tag B', Score: 18 },\n ]\n );\n const lines = csv.split('\\n');\n expect(lines).toHaveLength(3);\n expect(lines[0]).toBe('Name,Score');\n expect(lines[1]).toBe('Tag A,42');\n expect(lines[2]).toBe('Tag B,18');\n });\n\n it('should escape values containing commas', () => {\n const csv = buildCSV(\n ['Name'],\n [{ Name: 'Doe, John' }]\n );\n expect(csv).toContain('\"Doe, John\"');\n });\n\n it('should escape values containing quotes', () => {\n const csv = buildCSV(\n ['Name'],\n [{ Name: 'He said \"hello\"' }]\n );\n expect(csv).toContain('\"He said \"\"hello\"\"\"');\n });\n\n it('should handle null values', () => {\n const csv = buildCSV(\n ['Name', 'Value'],\n [{ Name: 'Test', Value: null }]\n );\n const lines = csv.split('\\n');\n expect(lines[1]).toBe('Test,');\n });\n\n it('should not include hidden _RecordID/_EntityName columns', () => {\n // Columns array should NOT include _RecordID/_EntityName\n const columns = ['Tag', 'Usage Count'];\n const data = [\n { Tag: 'CRM', 'Usage Count': 42, _RecordID: 'abc-123', _EntityName: 'MJ: Tags' },\n ];\n\n const csv = buildCSV(columns, data);\n expect(csv).not.toContain('_RecordID');\n expect(csv).not.toContain('abc-123');\n expect(csv).toContain('CRM');\n expect(csv).toContain('42');\n });\n});\n\ndescribe('Analytics Resource — Drill-Down Actions (AN-1)', () => {\n it('should include _RecordID and _EntityName in drill-down data for navigable rows', () => {\n // Simulating what loadDrillDownData produces for kpi-totalTags\n const rawTag = { ID: 'tag-1', Name: 'CRM', DisplayName: 'CRM', Description: 'Customer RM', Parent: 'Root' };\n const drillDownRow = {\n 'Name': String(rawTag.Name),\n 'Display Name': String(rawTag.DisplayName),\n 'Description': String(rawTag.Description),\n 'Parent': String(rawTag.Parent),\n '_RecordID': String(rawTag.ID),\n '_EntityName': 'MJ: Tags',\n };\n\n expect(drillDownRow['_RecordID']).toBe('tag-1');\n expect(drillDownRow['_EntityName']).toBe('MJ: Tags');\n });\n\n it('should not include _RecordID for aggregate-only drill-downs', () => {\n // contentCoverage and qualityScore don't have individual records\n const coverageRow = {\n 'Content Type': 'Articles',\n 'Total Items': 100,\n 'Tagged Items': 85,\n 'Coverage %': '85%',\n };\n\n expect(coverageRow).not.toHaveProperty('_RecordID');\n expect(coverageRow).not.toHaveProperty('_EntityName');\n });\n});\n\ndescribe('Analytics Resource — Cost Aggregation (D1)', () => {\n // Re-implement the static SumField method from the component for testing\n function sumField(records: Record<string, unknown>[], fieldName: string): number {\n return records.reduce((sum, r) => sum + Number(r[fieldName] || 0), 0);\n }\n\n const mockDetails: Record<string, unknown>[] = [\n { ContentProcessRunID: 'run-1', TotalTokensUsed: 1500, TotalCost: 0.0045 },\n { ContentProcessRunID: 'run-1', TotalTokensUsed: 2500, TotalCost: 0.0075 },\n { ContentProcessRunID: 'run-2', TotalTokensUsed: 3000, TotalCost: 0.009 },\n ];\n\n it('should sum total tokens across all detail records', () => {\n const totalTokens = sumField(mockDetails, 'TotalTokensUsed');\n expect(totalTokens).toBe(7000);\n });\n\n it('should sum total cost across all detail records', () => {\n const totalCost = sumField(mockDetails, 'TotalCost');\n expect(totalCost).toBeCloseTo(0.021, 4);\n });\n\n it('should calculate average cost per run', () => {\n const totalCost = sumField(mockDetails, 'TotalCost');\n const runCount = 2; // Two unique runs\n const avgCostPerRun = runCount > 0 ? totalCost / runCount : 0;\n expect(avgCostPerRun).toBeCloseTo(0.0105, 4);\n });\n\n it('should handle empty records gracefully', () => {\n const totalTokens = sumField([], 'TotalTokensUsed');\n const totalCost = sumField([], 'TotalCost');\n expect(totalTokens).toBe(0);\n expect(totalCost).toBe(0);\n });\n\n it('should handle records with missing/null fields', () => {\n const records: Record<string, unknown>[] = [\n { ContentProcessRunID: 'run-1', TotalTokensUsed: null, TotalCost: undefined },\n { ContentProcessRunID: 'run-2' },\n ];\n expect(sumField(records, 'TotalTokensUsed')).toBe(0);\n expect(sumField(records, 'TotalCost')).toBe(0);\n });\n\n it('should filter details by run ID correctly', () => {\n const filteredRunIds = new Set(['run-1']);\n const filteredDetails = mockDetails.filter(\n d => filteredRunIds.has(String(d['ContentProcessRunID'] || ''))\n );\n const tokens = sumField(filteredDetails, 'TotalTokensUsed');\n expect(tokens).toBe(4000); // 1500 + 2500\n });\n});\n\ndescribe('Analytics Resource — Preference Persistence (SR-6)', () => {\n let mockStorage: Record<string, string>;\n\n beforeEach(() => {\n mockStorage = {};\n vi.stubGlobal('localStorage', {\n getItem: vi.fn((key: string) => mockStorage[key] ?? null),\n setItem: vi.fn((key: string, value: string) => { mockStorage[key] = value; }),\n removeItem: vi.fn((key: string) => { delete mockStorage[key]; }),\n });\n });\n\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it('should persist preferences to localStorage', () => {\n const prefs = {\n ActiveTab: 'tags',\n ActiveDateRange: '90D',\n EntityFilter: 'Contacts',\n };\n localStorage.setItem('KH_AnalyticsPreferences', JSON.stringify(prefs));\n\n const stored = JSON.parse(localStorage.getItem('KH_AnalyticsPreferences')!);\n expect(stored.ActiveTab).toBe('tags');\n expect(stored.ActiveDateRange).toBe('90D');\n expect(stored.EntityFilter).toBe('Contacts');\n });\n\n it('should load preferences from localStorage', () => {\n const prefs = { ActiveTab: 'pipeline', ActiveDateRange: '7D', EntityFilter: 'All Entities' };\n mockStorage['KH_AnalyticsPreferences'] = JSON.stringify(prefs);\n\n const raw = localStorage.getItem('KH_AnalyticsPreferences');\n expect(raw).not.toBeNull();\n const parsed = JSON.parse(raw!);\n expect(parsed.ActiveTab).toBe('pipeline');\n });\n\n it('should handle missing localStorage gracefully', () => {\n const raw = localStorage.getItem('KH_AnalyticsPreferences');\n expect(raw).toBeNull();\n // Component should use defaults when no stored prefs\n });\n\n it('should store search preferences separately', () => {\n const searchPrefs = { ShowFilters: true, MinScoreThreshold: 0.5 };\n localStorage.setItem('KH_SearchPreferences', JSON.stringify(searchPrefs));\n\n const analyticsPrefs = localStorage.getItem('KH_AnalyticsPreferences');\n expect(analyticsPrefs).toBeNull(); // Should not cross-pollinate\n\n const searchStored = JSON.parse(localStorage.getItem('KH_SearchPreferences')!);\n expect(searchStored.MinScoreThreshold).toBe(0.5);\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dashboards.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dashboards.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for dashboards package:
|
|
3
|
-
* - Verifies the package exports are accessible
|
|
4
|
-
* - Tests AI instrumentation service concepts where testable
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
7
|
-
// Mock Angular
|
|
8
|
-
vi.mock('@angular/core', () => ({
|
|
9
|
-
Injectable: () => (target) => target,
|
|
10
|
-
Component: () => (target) => target,
|
|
11
|
-
Directive: () => (target) => target,
|
|
12
|
-
NgModule: () => (target) => target,
|
|
13
|
-
Input: () => () => { },
|
|
14
|
-
Output: () => () => { },
|
|
15
|
-
EventEmitter: class {
|
|
16
|
-
emit() { }
|
|
17
|
-
},
|
|
18
|
-
ChangeDetectorRef: class {
|
|
19
|
-
detectChanges() { }
|
|
20
|
-
markForCheck() { }
|
|
21
|
-
},
|
|
22
|
-
ChangeDetectionStrategy: { OnPush: 1 },
|
|
23
|
-
ViewChild: () => () => { },
|
|
24
|
-
ElementRef: class {
|
|
25
|
-
},
|
|
26
|
-
OnInit: class {
|
|
27
|
-
},
|
|
28
|
-
OnDestroy: class {
|
|
29
|
-
},
|
|
30
|
-
Injector: class {
|
|
31
|
-
},
|
|
32
|
-
ViewEncapsulation: { None: 0 },
|
|
33
|
-
}));
|
|
34
|
-
describe('dashboards package', () => {
|
|
35
|
-
it('should define workspace types for dashboards', async () => {
|
|
36
|
-
// Verify the module structure exists
|
|
37
|
-
expect(true).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
//# sourceMappingURL=dashboards.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dashboards.test.js","sourceRoot":"","sources":["../../src/__tests__/dashboards.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,eAAe;AACf,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC9C,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC7C,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC7C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM;IAC5C,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;IACrB,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;IACtB,YAAY,EAAE;QAAQ,IAAI,KAAI,CAAC;KAAE;IACjC,iBAAiB,EAAE;QAAQ,aAAa,KAAI,CAAC;QAAC,YAAY,KAAI,CAAC;KAAE;IACjE,uBAAuB,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;IACtC,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC;IACzB,UAAU,EAAE;KAAQ;IACpB,MAAM,EAAE;KAAQ;IAChB,SAAS,EAAE;KAAQ;IACnB,QAAQ,EAAE;KAAQ;IAClB,iBAAiB,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;CAC/B,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,qCAAqC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Tests for dashboards package:\n * - Verifies the package exports are accessible\n * - Tests AI instrumentation service concepts where testable\n */\nimport { describe, it, expect, vi } from 'vitest';\n\n// Mock Angular\nvi.mock('@angular/core', () => ({\n Injectable: () => (target: Function) => target,\n Component: () => (target: Function) => target,\n Directive: () => (target: Function) => target,\n NgModule: () => (target: Function) => target,\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n ChangeDetectorRef: class { detectChanges() {} markForCheck() {} },\n ChangeDetectionStrategy: { OnPush: 1 },\n ViewChild: () => () => {},\n ElementRef: class {},\n OnInit: class {},\n OnDestroy: class {},\n Injector: class {},\n ViewEncapsulation: { None: 0 },\n}));\n\ndescribe('dashboards package', () => {\n it('should define workspace types for dashboards', async () => {\n // Verify the module structure exists\n expect(true).toBe(true);\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"integration-data-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/integration-data-service.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for IntegrationDataService
|
|
3
|
-
* Covers pure logic methods (KPI computation, formatting, status calculation)
|
|
4
|
-
*/
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import { IntegrationDataService } from '../Integration/services/integration-data.service';
|
|
7
|
-
function createService() {
|
|
8
|
-
return new IntegrationDataService();
|
|
9
|
-
}
|
|
10
|
-
function createSummary(overrides = {}) {
|
|
11
|
-
return {
|
|
12
|
-
Integration: {
|
|
13
|
-
ID: '1', Name: 'Test', IsActive: true, LastRunID: null,
|
|
14
|
-
LastRunStartedAt: null, LastRunEndedAt: null, Company: 'TestCo',
|
|
15
|
-
Integration: 'Test', DriverClassName: null
|
|
16
|
-
},
|
|
17
|
-
SourceType: null,
|
|
18
|
-
LatestRun: null,
|
|
19
|
-
RecentRuns: [],
|
|
20
|
-
StatusColor: 'gray',
|
|
21
|
-
RelativeTime: 'Never run',
|
|
22
|
-
TotalRecordsSyncedToday: 0,
|
|
23
|
-
TotalErrors: 0,
|
|
24
|
-
DurationMs: null,
|
|
25
|
-
Icon: null,
|
|
26
|
-
...overrides
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function createRun(overrides = {}) {
|
|
30
|
-
return {
|
|
31
|
-
ID: '1', CompanyIntegrationID: '1', StartedAt: null, EndedAt: null,
|
|
32
|
-
TotalRecords: 0, Status: 'Success', ErrorLog: null,
|
|
33
|
-
Integration: 'Test', Company: 'TestCo', RunByUser: 'Admin',
|
|
34
|
-
...overrides
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
describe('IntegrationDataService', () => {
|
|
38
|
-
describe('ComputeKPIs', () => {
|
|
39
|
-
it('should return zeros for empty summaries', () => {
|
|
40
|
-
const svc = createService();
|
|
41
|
-
const kpis = svc.ComputeKPIs([]);
|
|
42
|
-
expect(kpis.TotalIntegrations).toBe(0);
|
|
43
|
-
expect(kpis.ActiveSyncs).toBe(0);
|
|
44
|
-
expect(kpis.RecordsSyncedToday).toBe(0);
|
|
45
|
-
expect(kpis.ErrorRate).toBe(0);
|
|
46
|
-
expect(kpis.AverageSyncDurationMs).toBeNull();
|
|
47
|
-
});
|
|
48
|
-
it('should count total integrations', () => {
|
|
49
|
-
const svc = createService();
|
|
50
|
-
const summaries = [createSummary(), createSummary(), createSummary()];
|
|
51
|
-
const kpis = svc.ComputeKPIs(summaries);
|
|
52
|
-
expect(kpis.TotalIntegrations).toBe(3);
|
|
53
|
-
});
|
|
54
|
-
it('should count active syncs (In Progress and Pending)', () => {
|
|
55
|
-
const svc = createService();
|
|
56
|
-
const summaries = [
|
|
57
|
-
createSummary({ LatestRun: createRun({ Status: 'In Progress' }) }),
|
|
58
|
-
createSummary({ LatestRun: createRun({ Status: 'Pending' }) }),
|
|
59
|
-
createSummary({ LatestRun: createRun({ Status: 'Success' }) }),
|
|
60
|
-
];
|
|
61
|
-
const kpis = svc.ComputeKPIs(summaries);
|
|
62
|
-
expect(kpis.ActiveSyncs).toBe(2);
|
|
63
|
-
});
|
|
64
|
-
it('should sum records synced today', () => {
|
|
65
|
-
const svc = createService();
|
|
66
|
-
const summaries = [
|
|
67
|
-
createSummary({ TotalRecordsSyncedToday: 100 }),
|
|
68
|
-
createSummary({ TotalRecordsSyncedToday: 250 }),
|
|
69
|
-
];
|
|
70
|
-
const kpis = svc.ComputeKPIs(summaries);
|
|
71
|
-
expect(kpis.RecordsSyncedToday).toBe(350);
|
|
72
|
-
});
|
|
73
|
-
it('should compute error rate from recent runs', () => {
|
|
74
|
-
const svc = createService();
|
|
75
|
-
const runs = [createRun(), createRun(), createRun(), createRun()];
|
|
76
|
-
const summaries = [
|
|
77
|
-
createSummary({ RecentRuns: runs, TotalErrors: 1 }),
|
|
78
|
-
];
|
|
79
|
-
const kpis = svc.ComputeKPIs(summaries);
|
|
80
|
-
expect(kpis.ErrorRate).toBe(25);
|
|
81
|
-
});
|
|
82
|
-
it('should compute average sync duration', () => {
|
|
83
|
-
const svc = createService();
|
|
84
|
-
const summaries = [
|
|
85
|
-
createSummary({ DurationMs: 1000 }),
|
|
86
|
-
createSummary({ DurationMs: 3000 }),
|
|
87
|
-
createSummary({ DurationMs: null }),
|
|
88
|
-
];
|
|
89
|
-
const kpis = svc.ComputeKPIs(summaries);
|
|
90
|
-
expect(kpis.AverageSyncDurationMs).toBe(2000);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
describe('FormatDuration', () => {
|
|
94
|
-
it('should return -- for null', () => {
|
|
95
|
-
expect(createService().FormatDuration(null)).toBe('--');
|
|
96
|
-
});
|
|
97
|
-
it('should format seconds', () => {
|
|
98
|
-
expect(createService().FormatDuration(45000)).toBe('45s');
|
|
99
|
-
});
|
|
100
|
-
it('should format minutes and seconds', () => {
|
|
101
|
-
expect(createService().FormatDuration(125000)).toBe('2m 5s');
|
|
102
|
-
});
|
|
103
|
-
it('should format hours and minutes', () => {
|
|
104
|
-
expect(createService().FormatDuration(3720000)).toBe('1h 2m');
|
|
105
|
-
});
|
|
106
|
-
it('should format zero seconds', () => {
|
|
107
|
-
expect(createService().FormatDuration(0)).toBe('0s');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe('ComputeRelativeTime', () => {
|
|
111
|
-
it('should return "Never run" for null', () => {
|
|
112
|
-
expect(createService().ComputeRelativeTime(null)).toBe('Never run');
|
|
113
|
-
});
|
|
114
|
-
it('should return "Just now" for recent dates', () => {
|
|
115
|
-
const now = new Date().toISOString();
|
|
116
|
-
expect(createService().ComputeRelativeTime(now)).toBe('Just now');
|
|
117
|
-
});
|
|
118
|
-
it('should return minutes for recent past', () => {
|
|
119
|
-
const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
|
|
120
|
-
expect(createService().ComputeRelativeTime(fiveMinAgo)).toBe('5m ago');
|
|
121
|
-
});
|
|
122
|
-
it('should return hours for hours ago', () => {
|
|
123
|
-
const threeHoursAgo = new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString();
|
|
124
|
-
expect(createService().ComputeRelativeTime(threeHoursAgo)).toBe('3h ago');
|
|
125
|
-
});
|
|
126
|
-
it('should return days for days ago', () => {
|
|
127
|
-
const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();
|
|
128
|
-
expect(createService().ComputeRelativeTime(twoDaysAgo)).toBe('2d ago');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
//# sourceMappingURL=integration-data-service.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"integration-data-service.test.js","sourceRoot":"","sources":["../../src/__tests__/integration-data-service.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tests for IntegrationDataService\n * Covers pure logic methods (KPI computation, formatting, status calculation)\n */\nimport { describe, it, expect } from 'vitest';\nimport { IntegrationDataService, IntegrationSummary, IntegrationRunRow } from '../Integration/services/integration-data.service';\nimport { MJCompanyIntegrationEntity } from '@memberjunction/core-entities';\n\nfunction createService(): IntegrationDataService {\n return new IntegrationDataService();\n}\n\nfunction createSummary(overrides: Partial<IntegrationSummary> = {}): IntegrationSummary {\n return {\n Integration: {\n ID: '1', Name: 'Test', IsActive: true, LastRunID: null,\n LastRunStartedAt: null, LastRunEndedAt: null, Company: 'TestCo',\n Integration: 'Test', DriverClassName: null\n } as unknown as MJCompanyIntegrationEntity,\n SourceType: null,\n LatestRun: null,\n RecentRuns: [],\n StatusColor: 'gray',\n RelativeTime: 'Never run',\n TotalRecordsSyncedToday: 0,\n TotalErrors: 0,\n DurationMs: null,\n Icon: null,\n ...overrides\n };\n}\n\nfunction createRun(overrides: Partial<IntegrationRunRow> = {}): IntegrationRunRow {\n return {\n ID: '1', CompanyIntegrationID: '1', StartedAt: null, EndedAt: null,\n TotalRecords: 0, Status: 'Success', ErrorLog: null,\n Integration: 'Test', Company: 'TestCo', RunByUser: 'Admin',\n ...overrides\n };\n}\n\ndescribe('IntegrationDataService', () => {\n describe('ComputeKPIs', () => {\n it('should return zeros for empty summaries', () => {\n const svc = createService();\n const kpis = svc.ComputeKPIs([]);\n expect(kpis.TotalIntegrations).toBe(0);\n expect(kpis.ActiveSyncs).toBe(0);\n expect(kpis.RecordsSyncedToday).toBe(0);\n expect(kpis.ErrorRate).toBe(0);\n expect(kpis.AverageSyncDurationMs).toBeNull();\n });\n\n it('should count total integrations', () => {\n const svc = createService();\n const summaries = [createSummary(), createSummary(), createSummary()];\n const kpis = svc.ComputeKPIs(summaries);\n expect(kpis.TotalIntegrations).toBe(3);\n });\n\n it('should count active syncs (In Progress and Pending)', () => {\n const svc = createService();\n const summaries = [\n createSummary({ LatestRun: createRun({ Status: 'In Progress' }) }),\n createSummary({ LatestRun: createRun({ Status: 'Pending' }) }),\n createSummary({ LatestRun: createRun({ Status: 'Success' }) }),\n ];\n const kpis = svc.ComputeKPIs(summaries);\n expect(kpis.ActiveSyncs).toBe(2);\n });\n\n it('should sum records synced today', () => {\n const svc = createService();\n const summaries = [\n createSummary({ TotalRecordsSyncedToday: 100 }),\n createSummary({ TotalRecordsSyncedToday: 250 }),\n ];\n const kpis = svc.ComputeKPIs(summaries);\n expect(kpis.RecordsSyncedToday).toBe(350);\n });\n\n it('should compute error rate from recent runs', () => {\n const svc = createService();\n const runs = [createRun(), createRun(), createRun(), createRun()];\n const summaries = [\n createSummary({ RecentRuns: runs, TotalErrors: 1 }),\n ];\n const kpis = svc.ComputeKPIs(summaries);\n expect(kpis.ErrorRate).toBe(25);\n });\n\n it('should compute average sync duration', () => {\n const svc = createService();\n const summaries = [\n createSummary({ DurationMs: 1000 }),\n createSummary({ DurationMs: 3000 }),\n createSummary({ DurationMs: null }),\n ];\n const kpis = svc.ComputeKPIs(summaries);\n expect(kpis.AverageSyncDurationMs).toBe(2000);\n });\n });\n\n describe('FormatDuration', () => {\n it('should return -- for null', () => {\n expect(createService().FormatDuration(null)).toBe('--');\n });\n\n it('should format seconds', () => {\n expect(createService().FormatDuration(45000)).toBe('45s');\n });\n\n it('should format minutes and seconds', () => {\n expect(createService().FormatDuration(125000)).toBe('2m 5s');\n });\n\n it('should format hours and minutes', () => {\n expect(createService().FormatDuration(3720000)).toBe('1h 2m');\n });\n\n it('should format zero seconds', () => {\n expect(createService().FormatDuration(0)).toBe('0s');\n });\n });\n\n describe('ComputeRelativeTime', () => {\n it('should return \"Never run\" for null', () => {\n expect(createService().ComputeRelativeTime(null)).toBe('Never run');\n });\n\n it('should return \"Just now\" for recent dates', () => {\n const now = new Date().toISOString();\n expect(createService().ComputeRelativeTime(now)).toBe('Just now');\n });\n\n it('should return minutes for recent past', () => {\n const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();\n expect(createService().ComputeRelativeTime(fiveMinAgo)).toBe('5m ago');\n });\n\n it('should return hours for hours ago', () => {\n const threeHoursAgo = new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString();\n expect(createService().ComputeRelativeTime(threeHoursAgo)).toBe('3h ago');\n });\n\n it('should return days for days ago', () => {\n const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString();\n expect(createService().ComputeRelativeTime(twoDaysAgo)).toBe('2d ago');\n });\n });\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mapping-validation.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mapping-validation.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for MappingWorkspace validation logic
|
|
3
|
-
* Covers: UnmappedRequiredCount, HasKeyField, MappingValidation, ActiveEditableFields
|
|
4
|
-
* Tests pure logic extracted from the component's computed properties.
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect } from 'vitest';
|
|
7
|
-
function createField(overrides = {}) {
|
|
8
|
-
return {
|
|
9
|
-
ID: 'fm-1',
|
|
10
|
-
SourceFieldName: 'source_field',
|
|
11
|
-
SourceFieldLabel: 'Source Field',
|
|
12
|
-
SourceFieldType: 'string',
|
|
13
|
-
DestinationFieldName: 'DestField',
|
|
14
|
-
DestinationFieldLabel: 'Dest Field',
|
|
15
|
-
IsKeyField: false,
|
|
16
|
-
IsRequired: false,
|
|
17
|
-
Direction: 'SourceToDest',
|
|
18
|
-
Status: 'Active',
|
|
19
|
-
IsNew: false,
|
|
20
|
-
IsDirty: false,
|
|
21
|
-
...overrides
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
// Replicates the component's computed properties as pure functions
|
|
25
|
-
function getActiveFields(fields) {
|
|
26
|
-
return fields.filter(f => f.Status !== 'Inactive');
|
|
27
|
-
}
|
|
28
|
-
function getUnmappedRequiredCount(fields) {
|
|
29
|
-
return fields.filter(f => f.Status === 'Active' && f.IsRequired && !f.DestinationFieldName).length;
|
|
30
|
-
}
|
|
31
|
-
function getHasKeyField(fields) {
|
|
32
|
-
return fields.some(f => f.Status === 'Active' && f.IsKeyField);
|
|
33
|
-
}
|
|
34
|
-
function getMappingValidation(fields) {
|
|
35
|
-
const warnings = [];
|
|
36
|
-
const unmappedRequired = getUnmappedRequiredCount(fields);
|
|
37
|
-
if (unmappedRequired > 0) {
|
|
38
|
-
warnings.push(`${unmappedRequired} required field(s) missing destination mapping`);
|
|
39
|
-
}
|
|
40
|
-
const activeFields = getActiveFields(fields);
|
|
41
|
-
if (!getHasKeyField(fields) && activeFields.length > 0) {
|
|
42
|
-
warnings.push('No key field configured — sync may create duplicates');
|
|
43
|
-
}
|
|
44
|
-
return { IsValid: warnings.length === 0, Warnings: warnings };
|
|
45
|
-
}
|
|
46
|
-
describe('MappingWorkspace Validation', () => {
|
|
47
|
-
describe('ActiveEditableFields', () => {
|
|
48
|
-
it('should return only active fields', () => {
|
|
49
|
-
const fields = [
|
|
50
|
-
createField({ Status: 'Active' }),
|
|
51
|
-
createField({ Status: 'Inactive' }),
|
|
52
|
-
createField({ Status: 'Active' }),
|
|
53
|
-
];
|
|
54
|
-
expect(getActiveFields(fields)).toHaveLength(2);
|
|
55
|
-
});
|
|
56
|
-
it('should return empty array when all fields are inactive', () => {
|
|
57
|
-
const fields = [
|
|
58
|
-
createField({ Status: 'Inactive' }),
|
|
59
|
-
createField({ Status: 'Inactive' }),
|
|
60
|
-
];
|
|
61
|
-
expect(getActiveFields(fields)).toHaveLength(0);
|
|
62
|
-
});
|
|
63
|
-
it('should return empty array for no fields', () => {
|
|
64
|
-
expect(getActiveFields([])).toHaveLength(0);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
describe('UnmappedRequiredCount', () => {
|
|
68
|
-
it('should return 0 when no required fields are unmapped', () => {
|
|
69
|
-
const fields = [
|
|
70
|
-
createField({ IsRequired: true, DestinationFieldName: 'Mapped' }),
|
|
71
|
-
createField({ IsRequired: false, DestinationFieldName: '' }),
|
|
72
|
-
];
|
|
73
|
-
expect(getUnmappedRequiredCount(fields)).toBe(0);
|
|
74
|
-
});
|
|
75
|
-
it('should count required fields with empty destination', () => {
|
|
76
|
-
const fields = [
|
|
77
|
-
createField({ IsRequired: true, DestinationFieldName: '' }),
|
|
78
|
-
createField({ IsRequired: true, DestinationFieldName: '' }),
|
|
79
|
-
createField({ IsRequired: true, DestinationFieldName: 'Name' }),
|
|
80
|
-
];
|
|
81
|
-
expect(getUnmappedRequiredCount(fields)).toBe(2);
|
|
82
|
-
});
|
|
83
|
-
it('should ignore inactive required fields', () => {
|
|
84
|
-
const fields = [
|
|
85
|
-
createField({ IsRequired: true, DestinationFieldName: '', Status: 'Inactive' }),
|
|
86
|
-
createField({ IsRequired: true, DestinationFieldName: '' }),
|
|
87
|
-
];
|
|
88
|
-
expect(getUnmappedRequiredCount(fields)).toBe(1);
|
|
89
|
-
});
|
|
90
|
-
it('should return 0 for empty field list', () => {
|
|
91
|
-
expect(getUnmappedRequiredCount([])).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
describe('HasKeyField', () => {
|
|
95
|
-
it('should return true when an active key field exists', () => {
|
|
96
|
-
const fields = [
|
|
97
|
-
createField({ IsKeyField: false }),
|
|
98
|
-
createField({ IsKeyField: true }),
|
|
99
|
-
];
|
|
100
|
-
expect(getHasKeyField(fields)).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
it('should return false when no key fields exist', () => {
|
|
103
|
-
const fields = [
|
|
104
|
-
createField({ IsKeyField: false }),
|
|
105
|
-
createField({ IsKeyField: false }),
|
|
106
|
-
];
|
|
107
|
-
expect(getHasKeyField(fields)).toBe(false);
|
|
108
|
-
});
|
|
109
|
-
it('should ignore inactive key fields', () => {
|
|
110
|
-
const fields = [
|
|
111
|
-
createField({ IsKeyField: true, Status: 'Inactive' }),
|
|
112
|
-
];
|
|
113
|
-
expect(getHasKeyField(fields)).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
it('should return false for empty list', () => {
|
|
116
|
-
expect(getHasKeyField([])).toBe(false);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
describe('MappingValidation', () => {
|
|
120
|
-
it('should be valid when all required fields are mapped and a key exists', () => {
|
|
121
|
-
const fields = [
|
|
122
|
-
createField({ IsRequired: true, DestinationFieldName: 'Email', IsKeyField: true }),
|
|
123
|
-
createField({ IsRequired: false, DestinationFieldName: 'Name' }),
|
|
124
|
-
];
|
|
125
|
-
const result = getMappingValidation(fields);
|
|
126
|
-
expect(result.IsValid).toBe(true);
|
|
127
|
-
expect(result.Warnings).toHaveLength(0);
|
|
128
|
-
});
|
|
129
|
-
it('should warn about unmapped required fields', () => {
|
|
130
|
-
const fields = [
|
|
131
|
-
createField({ IsRequired: true, DestinationFieldName: '', IsKeyField: true }),
|
|
132
|
-
];
|
|
133
|
-
const result = getMappingValidation(fields);
|
|
134
|
-
expect(result.IsValid).toBe(false);
|
|
135
|
-
expect(result.Warnings).toHaveLength(1);
|
|
136
|
-
expect(result.Warnings[0]).toContain('1 required field(s) missing');
|
|
137
|
-
});
|
|
138
|
-
it('should warn about missing key field when active fields exist', () => {
|
|
139
|
-
const fields = [
|
|
140
|
-
createField({ IsKeyField: false, DestinationFieldName: 'Name' }),
|
|
141
|
-
];
|
|
142
|
-
const result = getMappingValidation(fields);
|
|
143
|
-
expect(result.IsValid).toBe(false);
|
|
144
|
-
expect(result.Warnings).toHaveLength(1);
|
|
145
|
-
expect(result.Warnings[0]).toContain('No key field configured');
|
|
146
|
-
});
|
|
147
|
-
it('should report both warnings when both issues exist', () => {
|
|
148
|
-
const fields = [
|
|
149
|
-
createField({ IsRequired: true, DestinationFieldName: '', IsKeyField: false }),
|
|
150
|
-
createField({ IsRequired: false, DestinationFieldName: 'Name', IsKeyField: false }),
|
|
151
|
-
];
|
|
152
|
-
const result = getMappingValidation(fields);
|
|
153
|
-
expect(result.IsValid).toBe(false);
|
|
154
|
-
expect(result.Warnings).toHaveLength(2);
|
|
155
|
-
});
|
|
156
|
-
it('should be valid (no warnings) for empty field list', () => {
|
|
157
|
-
const result = getMappingValidation([]);
|
|
158
|
-
expect(result.IsValid).toBe(true);
|
|
159
|
-
expect(result.Warnings).toHaveLength(0);
|
|
160
|
-
});
|
|
161
|
-
it('should not warn about key field when no active fields exist', () => {
|
|
162
|
-
const fields = [
|
|
163
|
-
createField({ Status: 'Inactive', IsKeyField: false }),
|
|
164
|
-
];
|
|
165
|
-
const result = getMappingValidation(fields);
|
|
166
|
-
expect(result.IsValid).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
//# sourceMappingURL=mapping-validation.test.js.map
|