Add vendor-specific issue type dropdown for Jira ticket creation

When the Project Key field contains a vendor project key (e.g. AA_VECIMA),
the Issue Type dropdown switches from STEAM types (Story, Epic, Program,
Project, Reservation, Automation Maintenance) to vendor types (Epic, Story,
Task, Defect, Production Defect/Incident Fix, New Feature, Spike, Release
Candidate, Documentation).

- Add VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES constants
- Add isVendorProject() and getIssueTypesForProject() pure functions
- Update JiraPage modal with context-aware dropdown and reset on switch
- Update Ivanti queue modal with project_key and issue_type fields
- Add property-based tests for determination logic and state transitions
This commit is contained in:
Jordan Ramos
2026-05-27 15:08:08 -06:00
parent 56e3f5f973
commit 04eb21a7d3
3 changed files with 301 additions and 9 deletions

View File

@@ -0,0 +1,211 @@
/**
* Property-Based Tests: Vendor Issue Type Dropdown
*
* Feature: vendor-issue-type-dropdown
*
* Tests the pure determination logic that decides which issue type list
* to display based on the project key input.
*
* Validates: Requirements 1.2, 1.3, 1.4, 1.5, 2.1, 2.3, 3.4, 3.5, 4.1, 4.2, 6.3
*/
const fc = require('fast-check');
// ---------------------------------------------------------------------------
// Replicate the pure functions from JiraPage.js for testing
// ---------------------------------------------------------------------------
const VENDOR_PROJECT_KEYS = ['AA_VECIMA'];
const VENDOR_ISSUE_TYPES = [
'Epic',
'Story',
'Task',
'Defect',
'Production Defect/Incident Fix',
'New Feature',
'Spike',
'Release Candidate',
'Documentation',
];
const STEAM_ISSUE_TYPES = [
'Story',
'Epic',
'Program',
'Project',
'Reservation',
'Automation Maintenance',
];
function isVendorProject(projectKey, vendorKeys) {
if (!projectKey || typeof projectKey !== 'string') return false;
const normalized = projectKey.trim().toUpperCase();
if (normalized.length === 0) return false;
return vendorKeys.includes(normalized);
}
function getIssueTypesForProject(projectKey, vendorKeys, vendorTypes, steamTypes) {
return isVendorProject(projectKey, vendorKeys) ? vendorTypes : steamTypes;
}
/**
* Simulates the project_key onChange logic: resets issue_type only on context switch.
*/
function simulateProjectKeyChange(oldKey, newKey, currentIssueType, vendorKeys) {
const wasVendor = isVendorProject(oldKey, vendorKeys);
const isNowVendor = isVendorProject(newKey, vendorKeys);
return (wasVendor !== isNowVendor) ? '' : currentIssueType;
}
// ---------------------------------------------------------------------------
// Property 1: Issue type list determination
// ---------------------------------------------------------------------------
describe('Feature: vendor-issue-type-dropdown, Property 1: Issue type list determination', () => {
it('returns VENDOR_ISSUE_TYPES when project key matches a vendor key (case-insensitive, trimmed)', () => {
// Generate variations of the vendor key with different casing and whitespace
const vendorKeyVariants = fc.oneof(
fc.constant('AA_VECIMA'),
fc.constant('aa_vecima'),
fc.constant('Aa_Vecima'),
fc.constant(' AA_VECIMA '),
fc.constant('aa_VECIMA'),
);
fc.assert(
fc.property(vendorKeyVariants, (key) => {
const result = getIssueTypesForProject(key, VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES);
expect(result).toBe(VENDOR_ISSUE_TYPES);
}),
{ numRuns: 100 }
);
});
it('returns STEAM_ISSUE_TYPES for any string that does not match a vendor key after normalization', () => {
// Generate arbitrary strings that are NOT 'AA_VECIMA' after trim+uppercase
const nonVendorKey = fc.string({ minLength: 0, maxLength: 50 }).filter(s => {
const normalized = s.trim().toUpperCase();
return normalized !== 'AA_VECIMA';
});
fc.assert(
fc.property(nonVendorKey, (key) => {
const result = getIssueTypesForProject(key, VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES);
expect(result).toBe(STEAM_ISSUE_TYPES);
}),
{ numRuns: 100 }
);
});
it('returns STEAM_ISSUE_TYPES for null, undefined, and empty string', () => {
const emptyInputs = fc.oneof(
fc.constant(null),
fc.constant(undefined),
fc.constant(''),
fc.constant(' '),
);
fc.assert(
fc.property(emptyInputs, (key) => {
const result = getIssueTypesForProject(key, VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES);
expect(result).toBe(STEAM_ISSUE_TYPES);
}),
{ numRuns: 100 }
);
});
});
// ---------------------------------------------------------------------------
// Property 2: Context switch resets issue type selection
// ---------------------------------------------------------------------------
describe('Feature: vendor-issue-type-dropdown, Property 2: Context switch resets issue type', () => {
it('resets issue_type to empty when switching from vendor to non-vendor context', () => {
// Generate a vendor key variant and a non-vendor key
const vendorKey = fc.oneof(
fc.constant('AA_VECIMA'),
fc.constant('aa_vecima'),
fc.constant(' AA_VECIMA '),
);
const nonVendorKey = fc.string({ minLength: 1, maxLength: 30 }).filter(s => {
const normalized = s.trim().toUpperCase();
return normalized !== 'AA_VECIMA' && normalized.length > 0;
});
const anyIssueType = fc.string({ minLength: 1, maxLength: 50 });
fc.assert(
fc.property(vendorKey, nonVendorKey, anyIssueType, (oldKey, newKey, issueType) => {
const result = simulateProjectKeyChange(oldKey, newKey, issueType, VENDOR_PROJECT_KEYS);
expect(result).toBe('');
}),
{ numRuns: 100 }
);
});
it('resets issue_type to empty when switching from non-vendor to vendor context', () => {
const nonVendorKey = fc.string({ minLength: 1, maxLength: 30 }).filter(s => {
const normalized = s.trim().toUpperCase();
return normalized !== 'AA_VECIMA' && normalized.length > 0;
});
const vendorKey = fc.oneof(
fc.constant('AA_VECIMA'),
fc.constant('aa_vecima'),
);
const anyIssueType = fc.string({ minLength: 1, maxLength: 50 });
fc.assert(
fc.property(nonVendorKey, vendorKey, anyIssueType, (oldKey, newKey, issueType) => {
const result = simulateProjectKeyChange(oldKey, newKey, issueType, VENDOR_PROJECT_KEYS);
expect(result).toBe('');
}),
{ numRuns: 100 }
);
});
});
// ---------------------------------------------------------------------------
// Property 3: Same context preserves issue type selection
// ---------------------------------------------------------------------------
describe('Feature: vendor-issue-type-dropdown, Property 3: Same context preserves issue type', () => {
it('preserves issue_type when both old and new keys resolve to STEAM context', () => {
const nonVendorKey1 = fc.string({ minLength: 0, maxLength: 30 }).filter(s => {
const normalized = s.trim().toUpperCase();
return normalized !== 'AA_VECIMA';
});
const nonVendorKey2 = fc.string({ minLength: 0, maxLength: 30 }).filter(s => {
const normalized = s.trim().toUpperCase();
return normalized !== 'AA_VECIMA';
});
const anyIssueType = fc.string({ minLength: 0, maxLength: 50 });
fc.assert(
fc.property(nonVendorKey1, nonVendorKey2, anyIssueType, (oldKey, newKey, issueType) => {
const result = simulateProjectKeyChange(oldKey, newKey, issueType, VENDOR_PROJECT_KEYS);
expect(result).toBe(issueType);
}),
{ numRuns: 100 }
);
});
it('preserves issue_type when both old and new keys resolve to vendor context', () => {
// With only one vendor key, both must be variants of AA_VECIMA
const vendorKey1 = fc.oneof(
fc.constant('AA_VECIMA'),
fc.constant('aa_vecima'),
fc.constant(' AA_VECIMA '),
);
const vendorKey2 = fc.oneof(
fc.constant('AA_VECIMA'),
fc.constant('Aa_Vecima'),
fc.constant('aa_VECIMA'),
);
const anyIssueType = fc.string({ minLength: 0, maxLength: 50 });
fc.assert(
fc.property(vendorKey1, vendorKey2, anyIssueType, (oldKey, newKey, issueType) => {
const result = simulateProjectKeyChange(oldKey, newKey, issueType, VENDOR_PROJECT_KEYS);
expect(result).toBe(issueType);
}),
{ numRuns: 100 }
);
});
});