diff --git a/src/constants/part-number-strings.js b/src/constants/part-number-strings.js new file mode 100644 index 00000000..85e3398d --- /dev/null +++ b/src/constants/part-number-strings.js @@ -0,0 +1,5 @@ +const partNumberStrings = Object.freeze({ + RECYCLE_FEE: 'RECYCLE FEE' +}); + +export default partNumberStrings; diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 52a3a2c7..786a069d 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -4,7 +4,6 @@ RAIN_DEFENSE: 'RAIN DEFENSE', RECALIBRATION: 'RECALIBRATION', REPLACE_FEE: 'REPLACE FEE', - RECYCLE_FEE: 'RECYCLE FEE', MOBILE_FEE: 'MOBILE FEE', REPAIR_FEE: 'REPAIR FEE' }); diff --git a/src/helpers/price-calculator.spec.js b/src/helpers/price-calculator.spec.js index 4d09128b..5ef5669d 100644 --- a/src/helpers/price-calculator.spec.js +++ b/src/helpers/price-calculator.spec.js @@ -1,4 +1,4 @@ -import getPriceOfLineItems from "@/helpers/price-calculator.js"; +import getPriceOfLineItems from '@/helpers/price-calculator.js'; describe('getPriceOfLineItems', () => { test('Returns zero when no line items', () => { diff --git a/src/helpers/service-package-helper.js b/src/helpers/service-package-helper.js index 539bdeb3..128fd7b1 100644 --- a/src/helpers/service-package-helper.js +++ b/src/helpers/service-package-helper.js @@ -3,8 +3,8 @@ import damageLocationsSelected from '@/constants/damage-locations-selected'; import packageNames from '@/constants/package-names'; export function findLineItemsWithPartType(typeToFind, itemsToSearch) { - const partTypeMatches = itemsToSearch?.filter((lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()); - return partTypeMatches; + return itemsToSearch + ?.filter((lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()); } export function containsLineItemWithPartType(typeToFind, itemsToSearch) { @@ -55,7 +55,6 @@ export function shouldFrontWipersBeAvailable( export function shouldRearWipersBeAvailable( glassToReplace, availableLineItems, - isRepair, targetTier ) { const rearWiperIsAvailable = containsLineItemWithPartType( @@ -103,11 +102,13 @@ export function shouldRainDefenseBeAvailable( const rearWipersInTierTwo = shouldRearWipersBeAvailable( glassToReplace, availableLineItems, - isRepair, packageNames.TIER_TWO ); - return isTierThree && !(rearWipersInTierTwo && !frontWipersInTierTwo && frontWipersInTierThree); + return isTierThree + && !(rearWipersInTierTwo + && !frontWipersInTierTwo + && frontWipersInTierThree); } export function getPackageContents(glassToReplace, availableLineItems, isRepair, targetTier) { @@ -117,7 +118,7 @@ export function getPackageContents(glassToReplace, availableLineItems, isRepair, vaps.push(partTypeStrings.FRONT_WIPER); } - if (shouldRearWipersBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) { + if (shouldRearWipersBeAvailable(glassToReplace, availableLineItems, targetTier)) { vaps.push(partTypeStrings.REAR_WIPER); } diff --git a/src/helpers/service-package-helper.spec.js b/src/helpers/service-package-helper.spec.js new file mode 100644 index 00000000..878132dc --- /dev/null +++ b/src/helpers/service-package-helper.spec.js @@ -0,0 +1,1007 @@ +import * as servicePackageHelper from '@/helpers/service-package-helper'; +import partTypeStrings from '@/constants/part-type-strings.js'; +import damageLocationsSelected from '@/constants/damage-locations-selected.js'; +import packageNames from '@/constants/package-names.js'; + +const recalLineItem = { + partNumber: 'RECAL STATIC', + Description: 'Recalibration', + partType: 'recalibration', + Quantity: '1', + price: 150.0 +}; + +const driverFrontWiperLineItem = { + partNumber: 'SBB16', + description: 'SAFELITE BEAM BLADE 16', + partType: 'FRONT WIPER', + price: 32.64 +}; + +const passengerFrontWiperLineItem = { + partNumber: 'SBB26', + description: 'SAFELITE BEAM BLADE 26', + partType: 'FRONT WIPER', + price: 53.04 +}; + +const rearWiperLineItem = { + partNumber: 'SBBR12A', + description: 'SAFELITE REAR BLADE 12A', + partType: 'REAR WIPER', + price: 24.48 +}; + +const rainDefenseLineItem = { + partNumber: 'RAIN DEFENSE', + description: null, + partType: 'RAIN DEFENSE', + price: 35.5 +}; + +const frontWindshieldGlass = { + glassLocation: 'Windshield' +}; + +const rearWindshieldGlass = { + glassLocation: 'Rear' +}; + +const sideGlass = { + glassLocation: 'Driver' +}; + +describe('service-package-helper.js', () => { + describe('findLineItemsWithPartType', () => { + it('Returns an item of the same type when present', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + const partType = partTypeStrings.RAIN_DEFENSE; + + // Act + const result = servicePackageHelper.findLineItemsWithPartType(partType, lineItems); + + // Assert + expect(result.length).toBe(1); + expect(result[0].partType).toEqual(partType); + }); + + it('Returns an empty array when no matching items are present', () => { + // Arrange + const lineItems = [ + recalLineItem, + passengerFrontWiperLineItem, + driverFrontWiperLineItem + ]; + const partType = partTypeStrings.RAIN_DEFENSE; + + // Act + const result = servicePackageHelper.findLineItemsWithPartType(partType, lineItems); + + // Assert + expect(result.length).toBe(0); + }); + + it('Returns all matching items when multiple are present', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + const partType = partTypeStrings.FRONT_WIPER; + + // Act + const result = servicePackageHelper.findLineItemsWithPartType(partType, lineItems); + + // Assert + expect(result.length).toBe(2); + expect(result[0].partType).toEqual(partType); + expect(result[1].partType).toEqual(partType); + expect(result[0].partNumber).not.toEqual(result[1].partNumber); + }); + + it('Is not case sensitive', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + const partType = 'rAin DEfenSe'; + + // Act + const result = servicePackageHelper.findLineItemsWithPartType(partType, lineItems); + + // Assert + expect(result.length).toBe(1); + expect(result[0].partType).toEqual(partTypeStrings.RAIN_DEFENSE); + }); + }); + + describe('containsLineItemWithPartType', () => { + it('Returns true when type is present', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + const partType = partTypeStrings.RAIN_DEFENSE; + + // Act + const result = servicePackageHelper.containsLineItemWithPartType(partType, lineItems); + + // Assert + expect(result).toBe(true); + }); + + it('Returns true when type is present multiple times', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + const partType = partTypeStrings.FRONT_WIPER; + + // Act + const result = servicePackageHelper.containsLineItemWithPartType(partType, lineItems); + + // Assert + expect(result).toBe(true); + }); + + it('Returns false when type is not present', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem + ]; + const partType = partTypeStrings.RAIN_DEFENSE; + + // Act + const result = servicePackageHelper.containsLineItemWithPartType(partType, lineItems); + + // Assert + expect(result).toBe(false); + }); + + it('Returns false with empty array', () => { + // Arrange + const lineItems = []; + const partType = partTypeStrings.RAIN_DEFENSE; + + // Act + const result = servicePackageHelper.containsLineItemWithPartType(partType, lineItems); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('getVapsLineItems', () => { + it('Returns line items matching given types', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + + const vapTypes = [partTypeStrings.RAIN_DEFENSE]; + + // Act + const result = servicePackageHelper.getVapsLineItems(lineItems, vapTypes); + + // Assert + expect(result.length).toBe(1); + expect(result[0].partType).toEqual(partTypeStrings.RAIN_DEFENSE); + }); + + it('Returns all matching line items, when multiple match a single type', () => { + // Arrange + const lineItems = [ + recalLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + + const vapTypes = [partTypeStrings.FRONT_WIPER]; + + // Act + const result = servicePackageHelper.getVapsLineItems(lineItems, vapTypes); + + // Assert + expect(result.length).toBe(2); + expect(result[0].partType).toEqual(partTypeStrings.FRONT_WIPER); + expect(result[1].partType).toEqual(partTypeStrings.FRONT_WIPER); + expect(result[0].partNumber).not.toEqual(result[1].partNumber); + }); + + it('Returns only line items in the given set', () => { + // Arrange + const lineItems = [recalLineItem, passengerFrontWiperLineItem, rainDefenseLineItem]; + + const vapTypes = [partTypeStrings.FRONT_WIPER]; + + // Act + const result = servicePackageHelper.getVapsLineItems(lineItems, vapTypes); + + // Assert + expect(result.length).toBe(1); + expect(result[0].partType).toEqual(partTypeStrings.FRONT_WIPER); + expect(result[0].partNumber).toEqual(passengerFrontWiperLineItem.partNumber); + }); + + it('Returns all matching items when multiple vap types are requested', () => { + // Arrange + const lineItems = [recalLineItem, passengerFrontWiperLineItem, rainDefenseLineItem]; + + const vapTypes = [partTypeStrings.FRONT_WIPER, partTypeStrings.RAIN_DEFENSE]; + + // Act + const result = servicePackageHelper.getVapsLineItems(lineItems, vapTypes); + + // Assert + expect(result.length).toBe(2); + expect(result[0].partType).not.toEqual(result[1].partType); + }); + }); + + describe('containsGlassPieceWithLocation', () => { + it('Returns true when location is present', () => { + // Arrange + const locations = [frontWindshieldGlass, sideGlass]; + + const locationName = damageLocationsSelected.WINDSHIELD; + + // Act + const result = servicePackageHelper.containsGlassPieceWithLocation( + locationName, + locations + ); + + // Assert + expect(result).toBe(true); + }); + + it('Returns true when location is present multiple times', () => { + // Arrange + const locations = [frontWindshieldGlass, frontWindshieldGlass, sideGlass]; + + const locationName = damageLocationsSelected.WINDSHIELD; + + // Act + const result = servicePackageHelper.containsGlassPieceWithLocation( + locationName, + locations + ); + + // Assert + expect(result).toBe(true); + }); + + it('Returns false when location is not present', () => { + // Arrange + const locations = [frontWindshieldGlass, sideGlass]; + + const locationName = damageLocationsSelected.REAR; + + // Act + const result = servicePackageHelper.containsGlassPieceWithLocation( + locationName, + locations + ); + + // Assert + expect(result).toBe(false); + }); + + it('Returns false with empty array', () => { + // Arrange + const locations = []; + + const locationName = damageLocationsSelected.WINDSHIELD; + + // Act + const result = servicePackageHelper.containsGlassPieceWithLocation( + locationName, + locations + ); + + // Assert + expect(result).toBe(false); + }); + + it('Is not case sensitive', () => { + // Arrange + const locations = [frontWindshieldGlass, sideGlass]; + + const locationName = 'windShieLd'; + + // Act + const result = servicePackageHelper.containsGlassPieceWithLocation( + locationName, + locations + ); + + // Assert + expect(result).toBe(true); + }); + }); + + describe('getAvailablePackages', () => { + it('Returns two packages when there is not a viable tier-two option.', () => { + // Arrange + const damageLocations = [sideGlass]; + + const availableLineItems = [rainDefenseLineItem]; + + // Act + const results = servicePackageHelper.getAvailablePackages( + damageLocations, + availableLineItems, + false + ); + + // Assert + expect(results.length).toBe(2); + expect(results[0].packageName).toEqual(packageNames.TIER_ONE); + expect(results[1].packageName).toEqual(packageNames.TIER_THREE); + }); + + it('Returns three packages when wipers are available in tier two', () => { + // Arrange + const damageLocations = [rearWindshieldGlass]; + + const availableLineItems = [ + driverFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + + // Act + const results = servicePackageHelper.getAvailablePackages( + damageLocations, + availableLineItems, + true + ); + + // Assert + expect(results.length).toBe(3); + expect(results[0].packageName).toEqual(packageNames.TIER_ONE); + expect(results[1].packageName).toEqual(packageNames.TIER_TWO); + expect(results[2].packageName).toEqual(packageNames.TIER_THREE); + }); + }); + + // TODO write tests + describe('getPackageContents', () => { + describe('Match figma cases', () => { + test.todo('Implement specific cases.'); + }); + }); + + describe('shouldFrontWipersBeAvailable', () => { + it('Should be available in tier 2 and 3 if windshield damage and available', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + const isRepair = false; + + // Act + const resultTwo = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(resultTwo).toBe(true); + expect(resultThree).toBe(true); + }); + + it('Should be available in tier 2 and 3 if repair and available', () => { + // Arrange + const damageLocations = []; + const availableLineItems = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + const isRepair = true; + + // Act + const resultTwo = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(resultTwo).toBe(true); + expect(resultThree).toBe(true); + }); + + it('Should not be available in tier 2 if not windshield damage or repair', () => { + // Arrange + const damageLocations = []; + const availableLineItems = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + const isRepair = false; + + // Act + const result = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_TWO + ); + + // Assert + expect(result).toBe(false); + }); + + it('Should be available in tier 3 even if not windshield repair or replace', () => { + // Arrange + const damageLocations = []; + const availableLineItems = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + const isRepair = false; + + // Act + const result = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(result).toBe(true); + }); + + it('Should not be available in any tier if not available.', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = []; + const isRepair = true; + + // Act + const resultOne = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_ONE + ); + const resultTwo = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(resultOne).toBe(false); + expect(resultTwo).toBe(false); + expect(resultThree).toBe(false); + }); + + it('Should not be available in tier 1', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + const isRepair = true; + + // Act + const result = servicePackageHelper.shouldFrontWipersBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_ONE + ); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('shouldRearWipersBeAvailable', () => { + it('Should be available in tier 2 and 3 if rear damage and available', () => { + // Arrange + const damageLocations = [rearWindshieldGlass]; + const availableLineItems = [rearWiperLineItem]; + + // Act + const resultTwo = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_THREE + ); + + // Assert + expect(resultTwo).toBe(true); + expect(resultThree).toBe(true); + }); + + it('Should not be available in tier 2 if not rear damage', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [rearWiperLineItem]; + + // Act + const result = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_TWO + ); + + // Assert + expect(result).toBe(false); + }); + + it('Should be available in tier 3 without rear damage, if available and no front wipers available', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [rearWiperLineItem]; + + // Act + const result = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_THREE + ); + + // Assert + expect(result).toBe(true); + }); + + it('Should not be available in tier 3 without rear damage if front wipers are available', () => { + // Arrange + const damageLocations = []; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem + ]; + + // Act + const result = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_THREE + ); + + // Assert + expect(result).toBe(false); + }); + + it('Should not be available in any tier if not available', () => { + // Arrange + const damageLocations = [rearWindshieldGlass]; + const availableLineItems = []; + + // Act + const resultOne = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_ONE + ); + const resultTwo = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_THREE + ); + + // Assert + expect(resultOne).toBe(false); + expect(resultTwo).toBe(false); + expect(resultThree).toBe(false); + }); + + it('Should not be available in tier 1', () => { + // Arrange + const damageLocations = [rearWindshieldGlass]; + const availableLineItems = [rearWiperLineItem]; + + // Act + const result = servicePackageHelper.shouldRearWipersBeAvailable( + damageLocations, + availableLineItems, + packageNames.TIER_ONE + ); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('shouldRainDefenseBeAvailable', () => { + it('Should generally be available only in tier 3', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + rainDefenseLineItem, + driverFrontWiperLineItem, + rearWiperLineItem, + recalLineItem + ]; + const isRepair = false; + + // Act + const resultOne = servicePackageHelper.shouldRainDefenseBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_ONE + ); + const resultTwo = servicePackageHelper.shouldRainDefenseBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_TWO + ); + const resultThree = servicePackageHelper.shouldRainDefenseBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(resultOne).toBe(false); + expect(resultTwo).toBe(false); + expect(resultThree).toBe(true); + }); + + it('Should not be available in a specific scenario', () => { + // Arrange + // SPECIFICALLY: no front windshield + const damageLocations = [rearWindshieldGlass]; + const availableLineItems = [ + rainDefenseLineItem, + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem + ]; + // SPECIFICALLY: not repair + const isRepair = false; + + // Act + const result = servicePackageHelper.shouldRainDefenseBeAvailable( + damageLocations, + availableLineItems, + isRepair, + packageNames.TIER_THREE + ); + + // Assert + expect(result).toBe(false); + }); + }); + + describe('getLowestTierForType', () => { + it('Should return the tier for items available in one tier only', () => { + // Arrange + const damageLocations = [frontWindshieldGlass, rearWindshieldGlass]; + + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + + const isRepair = false; + + // Act + const result = servicePackageHelper.getLowestTierForType( + damageLocations, + availableLineItems, + isRepair, + partTypeStrings.RAIN_DEFENSE + ); + + // Assert + expect(result).toEqual(packageNames.TIER_THREE); + }); + + it('Should return tier 2 for items in tier 2 and 3', () => { + // Arrange + const damageLocations = [frontWindshieldGlass, rearWindshieldGlass]; + + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + + const isRepair = false; + + // Act + const resultFront = servicePackageHelper.getLowestTierForType( + damageLocations, + availableLineItems, + isRepair, + partTypeStrings.FRONT_WIPER + ); + const resultRear = servicePackageHelper.getLowestTierForType( + damageLocations, + availableLineItems, + isRepair, + partTypeStrings.REAR_WIPER + ); + + // Assert + expect(resultFront).toEqual(packageNames.TIER_TWO); + expect(resultRear).toEqual(packageNames.TIER_TWO); + }); + + it('Should return tier 1 for any item not in any tier', () => { + // Arrange + const damageLocations = [rearWindshieldGlass]; + + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem + ]; + + const isRepair = false; + + // Act + const resultFront = servicePackageHelper.getLowestTierForType( + damageLocations, + availableLineItems, + isRepair, + partTypeStrings.RAIN_DEFENSE + ); + + // Assert + expect(resultFront).toEqual(packageNames.TIER_ONE); + }); + }); + + describe('getHighestRequiredTier', () => { + it('Should return the highest tier among items', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem + ]; + + // Act + const result = servicePackageHelper.getHighestRequiredTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_THREE); + }); + + it('Should not return a higher tier than items require', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + + // Act + const result = servicePackageHelper.getHighestRequiredTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_TWO); + }); + + it('Should return tier 1 for empty set', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = []; + + // Act + const result = servicePackageHelper.getHighestRequiredTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_ONE); + }); + + it('Should return tier 1 if no vaps are part of tiers', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [recalLineItem]; + + // Act + const result = servicePackageHelper.getHighestRequiredTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_ONE); + }); + }); + + describe('getHighestSatisfiedTier', () => { + it('Should return the highest tier if multiple are satisfied', () => { + // Arrange + const damageLocations = [frontWindshieldGlass, rearWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rainDefenseLineItem, + rearWiperLineItem + ]; + + // Act + const result = servicePackageHelper.getHighestFullySatisfiedTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_THREE); + }); + + it('Should only return *satisfied* tier', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [driverFrontWiperLineItem, passengerFrontWiperLineItem]; + + // Act + const result = servicePackageHelper.getHighestFullySatisfiedTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_TWO); + }); + + it('Should not return a tier if it is only partially satisfied', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = [rainDefenseLineItem]; + + // Act + const result = servicePackageHelper.getHighestFullySatisfiedTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_ONE); + }); + + it('Should return tier 1 if no vaps are selected.', () => { + // Arrange + const damageLocations = [frontWindshieldGlass]; + const availableLineItems = [ + driverFrontWiperLineItem, + passengerFrontWiperLineItem, + rearWiperLineItem, + rainDefenseLineItem + ]; + const isRepair = false; + const vaps = []; + + // Act + const result = servicePackageHelper.getHighestFullySatisfiedTier( + damageLocations, + availableLineItems, + isRepair, + vaps + ); + + // Assert + expect(result).toEqual(packageNames.TIER_ONE); + }); + }); +}); diff --git a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap index e6e66827..ae4e42e9 100644 --- a/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap +++ b/src/iss-components/cart-dropdown/__snapshots__/cart-dropdown.spec.js.snap @@ -4,7 +4,10 @@ exports[`cart-dropdown component initial data rendered as expected 1`] = ` Object { "baseServiceLineItems": Array [], "deductible": null, + "glassToReplace": Array [], "isExpanded": false, + "isRepair": null, + "vapsInOrder": Array [], "widget": Object { "amountDue": "AmountDueTextWidget", "basePrice": "BasePriceWidget", @@ -12,7 +15,9 @@ Object { "mobileFee": "MobileServiceWidget", "recycleFee": "RecycleFeeWidget", "salesTax": "SalesTaxWidget", + "servicePackage": "ServicePackageTitle", "subtotal": "SubtotalWidget", + "vapsItemDescriptions": "VapsItemDescriptions", }, } `; diff --git a/src/iss-components/cart-dropdown/cart-dropdown.spec.js b/src/iss-components/cart-dropdown/cart-dropdown.spec.js index 5d125a0b..a123f42e 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.spec.js +++ b/src/iss-components/cart-dropdown/cart-dropdown.spec.js @@ -8,12 +8,21 @@ import { useMainStore } from '@/store'; import coverageStatuses from '@/constants/coverage-statuses'; import { formatAmountInDollars } from '@/helpers/text-helper.js'; import partTypeStrings from '@/constants/part-type-strings'; +import partNumberStrings from '@/constants/part-number-strings'; +import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js'; +import getPriceOfLineItems from '@/helpers/price-calculator.js'; const VERIFYING_COVERAGE = 'Verifying coverage'; jest.mock('@/helpers/text-helper', () => ({ formatAmountInDollars: jest.fn() })); +jest.mock('@/helpers/price-calculator.js', () => jest.fn()); + +jest.mock('@/helpers/service-package-helper', () => ({ + getHighestFullySatisfiedTier: jest.fn(), + getPackageContents: jest.fn() +})); function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}) { const mountOptions = getMountOptions({ @@ -34,8 +43,6 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData mountOptions.propsData = propsData; const wrapper = shallowMount(cartDropdown, mountOptions); - wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {}); - wrapper.vm.setCmsContent = jest.fn(); return { wrapper }; } @@ -122,6 +129,19 @@ describe('cart-dropdown component', () => { // Assert expect(cartBasePrice.exists()).toBeTruthy(); }); + test('service package', () => { + // Arrange + const reference = '#cart-service-package'; + const isExpanded = true; + const initialData = { isExpanded }; + const { wrapper } = getMountedComponent({}, {}, initialData); + + // Act + const servicePackage = wrapper.find(reference); + + // Assert + expect(servicePackage.exists()).toBeTruthy(); + }); test('cart footer', () => { // Arrange const reference = '#cart-footer'; @@ -161,6 +181,45 @@ describe('cart-dropdown component', () => { // Assert expect(salesTax.exists()).toBeTruthy(); }); + test('recycle fee when preset in supported items', () => { + // Arrange + const reference = '#recycle-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [{ + partNumber: partNumberStrings.RECYCLE_FEE + }] + } + } + }); + + // Act + const recycleFee = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).not.toBeNull(); + expect(recycleFee.exists()).toBeTruthy(); + }); + test('mobile fee when preset', () => { + // Arrange + const reference = '#mobile-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [], + mobileFee: {} + } + } + }); + + // Act + const mobileFee = wrapper.find(reference); + + // Assert + expect(wrapper.vm.mobileFeeCartItem).not.toBeNull(); + expect(mobileFee.exists()).toBeTruthy(); + }); test('bottom amount due', () => { // Arrange const reference = '#bottom-amount-due'; @@ -226,70 +285,45 @@ describe('cart-dropdown component', () => { // Assert expect(cartBasePrice.exists()).toBeFalsy(); }); + test('recycle fee when not in supported items', () => { + // Arrange + const reference = '#recycle-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [] + } + } + }); + + // Act + const recycleFee = wrapper.find(reference); + + // Assert + expect(wrapper.vm.recycleFeeCartItem).toBeNull(); + expect(recycleFee.exists()).toBeFalsy(); + }); + test('mobile fee when not in supported items', () => { + // Arrange + const reference = '#mobile-fee-item'; + const { wrapper } = getMountedComponent({ + order: { + lineItems: { + supportingItems: [], + mobileFee: null + } + } + }); + + // Act + const mobileFee = wrapper.find(reference); + + // Assert + expect(wrapper.vm.mobileFeeCartItem).toBeNull(); + expect(mobileFee.exists()).toBeFalsy(); + }); }); describe('computed', () => { - describe('showDeductibleLineItem', () => { - test.each([[true], [false]])('returns false when isNoComp true', (isItac) => { - // Arrange - const storeData = { - order: { - policy: { - isITAC: isItac, - noCoverage: true, - } - } - }; - const { wrapper } = getMountedComponent(storeData); - - // Act - const result = wrapper.vm.showDeductibleLineItem; - - // Assert - expect(result).toBeFalsy(); - }); - test.each([ - [coverageStatuses.NO_COMP], - [coverageStatuses.PENDING]])('returns false when isITAC true', (coverageStatus) => { - // Arrange - const storeData = { - order: { - payment: { - insuranceCoverage: { - coverageStatus - } - }, - policy: { - isITAC: true - } - } - }; - const { wrapper } = getMountedComponent(storeData); - - // Act - const result = wrapper.vm.showDeductibleLineItem; - - // Assert - expect(result).toBeFalsy(); - }); - test('returns true when isNoComp false and isITAC false', () => { - // Arrange - const storeData = { - order: { - policy: { - isITAC: false, - noCoverage: false - } - } - }; - const { wrapper } = getMountedComponent(storeData); - - // Act - const result = wrapper.vm.showDeductibleLineItem; - - // Assert - expect(result).toBeTruthy(); - }); - }); describe('isUnverified', () => { test('returns false when no comp', () => { // Arrange @@ -297,7 +331,7 @@ describe('cart-dropdown component', () => { order: { policy: { isITAC: false, - noCoverage: true, + noCoverage: true }, currentDeductible: null } @@ -423,6 +457,541 @@ describe('cart-dropdown component', () => { describe('salesTax', () => { // TODO when method implemented }); + describe('availableLineItems', () => { + test('returns expected when glassParts null', async () => { + // Arrange + const storeData = { + order: { + lineItems: { + glassParts: null, + supportingItems: [] + } + } + }; + const propsData = { availableVaps: [] }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual([]); + }); + test('returns expected when supportingItems null', async () => { + // Arrange + const storeData = { + order: { + lineItems: { + glassParts: [], + supportingItems: null + } + } + }; + const propsData = { availableVaps: [] }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual([]); + }); + test('returns expected when availableVaps null', async () => { + // Arrange + const storeData = { + order: { + lineItems: { + glassParts: [], + supportingItems: [] + } + } + }; + const propsData = { + availableVaps: undefined + }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual([]); + }); + test('returns expected when glassParts, supportingItems, and availableVaps have content', async () => { + // Arrange + const part1 = { partType: 'apple' }; + const part2 = { partType: 'banana' }; + const part3 = { partType: 'orange' }; + const part4 = { partType: 'grape' }; + const part5 = { partType: 'raspberry' }; + const storeData = { + order: { + lineItems: { + glassParts: [part1], + supportingItems: [part2, part3] + } + } + }; + const propsData = { + availableVaps: [part4, part5] + }; + const { wrapper } = getMountedComponent(storeData, {}, propsData); + const expected = [part1, part2, part3, part4, part5]; + + // Act + const result = wrapper.vm.availableLineItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + }); + describe('servicePackageCartItems', () => { + test.each([ + [undefined], [null], [[]] + ])('returns empty list when package contents %p', (packageContents) => { + // Arrange + getPackageContents.mockImplementationOnce(() => packageContents); + const { wrapper } = getMountedComponent(); + const expected = []; + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns front wiper when front wiper included in types', () => { + // Arrange + const frontWiperPartType = partTypeStrings.FRONT_WIPER; + const packageContents = [frontWiperPartType]; + getPackageContents.mockImplementation(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'some name'; + const vapsItemDescriptions = [ + { + Name: frontWiperPartType, + Text: expectedName + } + ]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? vapsItemDescriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: frontWiperPartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns rear wiper when rear wiper included in types', () => { + // Arrange + const rearWiperPartType = partTypeStrings.REAR_WIPER; + const packageContents = [rearWiperPartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'some other name'; + const vapsItemDescriptions = [ + { + Name: rearWiperPartType, + Text: expectedName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: rearWiperPartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns rain defense when rain defense included in types', () => { + // Arrange + const rainDefensePartType = partTypeStrings.RAIN_DEFENSE; + const packageContents = [rainDefensePartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'different name'; + const vapsItemDescriptions = [ + { + Name: rainDefensePartType, + Text: expectedName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: rainDefensePartType }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(1); + expect(result[0].name).toBe(expectedName); + }); + test('returns expected when all included in types', () => { + // Arrange + const frontWiperPartType = partTypeStrings.FRONT_WIPER; + const rearWiperPartType = partTypeStrings.REAR_WIPER; + const rainDefensePartType = partTypeStrings.RAIN_DEFENSE; + const packageContents = [frontWiperPartType, rearWiperPartType, rainDefensePartType]; + getPackageContents.mockImplementationOnce(() => packageContents); + + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedFrontWiperName = 'front wiper'; + const expectedRearWiperName = 'rear wiper'; + const expectedRainDefenseName = 'rain defense'; + const vapsItemDescriptions = [ + { + Name: frontWiperPartType, + Text: expectedFrontWiperName + }, + { + Name: rearWiperPartType, + Text: expectedRearWiperName + }, + { + Name: rainDefensePartType, + Text: expectedRainDefenseName + } + ]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => vapsItemDescriptions) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [ + { partType: frontWiperPartType }, + { partType: rearWiperPartType }, + { partType: rainDefensePartType } + ] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageCartItems; + + // Assert + expect(result.length).toBe(3); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedFrontWiperName + })); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedRearWiperName + })); + expect(result).toContainEqual(expect.objectContaining({ + name: expectedRainDefenseName + })); + }); + }); + describe('servicePackageLabelWidget', () => { + test.each([ + [null], + [undefined] + ])('returns "" when servicePackageNames %p', (servicePackageNames) => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns "" when servicePackageTier not found in servicePackageNames', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + getHighestFullySatisfiedTier.mockImplementationOnce(() => 'some other tier'); + const servicePackageNames = [{ Name: 'some tier' }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns "" when servicePackageTier has no SubWidgetName', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const tier = 'tier1'; + getHighestFullySatisfiedTier.mockImplementationOnce(() => tier); + const servicePackageNames = [{ Name: tier }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(''); + }); + test('returns expected when servicePackageTier has SubWidgetName', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const tier = 'tier1'; + getHighestFullySatisfiedTier.mockImplementationOnce(() => tier); + const expected = 'name of sub widget'; + const servicePackageNames = [{ + Name: tier, + SubWidgetName: expected + }]; + const mockMixin = { + methods: { + getCmsContent: jest.fn(() => servicePackageNames) + } + }; + mountOptions.mixins = [mockMixin]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.servicePackageLabelWidget; + + // Assert + expect(result).toBe(expected); + }); + }); + describe('recycleFeeCartItem', () => { + test.each([ + [null], [undefined], [[]] + ])('returns null when supporting items %p', (supportingItems) => { + // Arrange + const storeData = { + order: { + lineItems: { supportingItems } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns null when recycle fee not in non empty supporting items', () => { + // Arrange + const storeData = { + order: { + lineItems: { + supportingItems: [{ partNumber: 'not recycle' }] + } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when recycle fee in supporting items', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'recycling fee'; + const widgetName = 'RecycleFeeWidget'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? expectedName : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + supportingItems: [{ partNumber: partNumberStrings.RECYCLE_FEE }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.recycleFeeCartItem; + + // Assert + expect(result.name).toBe(expectedName); + }); + }); + describe('mobileFeeCartItem', () => { + test.each([[null], [undefined]])('returns null when mobile fee %p', (mobileFee) => { + // Arrange + const storeData = { + order: { + lineItems: { mobileFee } + } + }; + const { wrapper } = getMountedComponent(storeData); + const expected = null; + + // Act + const result = wrapper.vm.mobileFeeCartItem; + + // Assert + expect(result).toStrictEqual(expected); + }); + test('returns expected when mobile fee in supporting items', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const expectedName = 'mobile fee'; + const widgetName = 'MobileServiceWidget'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? expectedName : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + mobileFee: { partType: partTypeStrings.MOBILE_FEE } + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.mobileFeeCartItem; + + // Assert + expect(result.name).toBe(expectedName); + }); + }); }); describe('method', () => { describe('getDisplayed', () => { @@ -549,85 +1118,238 @@ describe('cart-dropdown component', () => { expect(result).toBe(dollarAmount); }); }); - describe('Recyle fee', () => { - test('Shows recycle fee when preset in supported items', () => { + describe('getCartItemForVapsPart', () => { + test('returns item with no label when no descriptions returned', () => { // Arrange - const reference = '#recycle-fee-item'; - const { wrapper } = getMountedComponent({ + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const descriptions = []; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const part = 'some part'; + const storeData = { order: { lineItems: { - supportingItems: [{ - partType: partTypeStrings.REPLACE_FEE - }] + vaps: [{ partType: part }] } } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + const expectedName = ''; // Act - const cartDeductible = wrapper.find(reference); + const result = wrapper.vm.getCartItemForVapsPart(part); // Assert - expect(wrapper.vm.recycleFeeCartItem).not.toBeNull(); - expect(cartDeductible.exists()).toBeTruthy(); + expect(result.name).toBe(expectedName); }); - test('Hides recycle fee when not in supported items', () => { + test('returns item with no label when type not in descriptions', () => { // Arrange - const reference = '#recycle-fee-item'; - const { wrapper } = getMountedComponent({ + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const descriptions = [{ + Name: 'not part name' + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const part = 'some part'; + const storeData = { order: { lineItems: { - supportingItems: [] + vaps: [{ partType: part }] } } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + const expectedName = ''; // Act - const cartDeductible = wrapper.find(reference); + const result = wrapper.vm.getCartItemForVapsPart(part); // Assert - expect(wrapper.vm.recycleFeeCartItem).toBeNull(); - expect(cartDeductible.exists()).toBeFalsy(); + expect(result.name).toBe(expectedName); + }); + test('returns item with no label when description text not defined', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const descriptions = [{ + Name: part + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(cartDropdown, mountOptions); + const expectedName = ''; + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test('returns expected label when vaps description text set', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const expectedName = 'name to show up'; + const descriptions = [{ + Name: part, + Text: expectedName + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: [{ partType: part }] + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.name).toBe(expectedName); + }); + test.each([ + [undefined], [null] + ])('returns null when vapsInOrder %p', async (vapsInOrder) => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const expectedName = 'name to show up'; + const descriptions = [{ + Name: part, + Text: expectedName + }]; + const widgetName = 'VapsItemDescriptions'; + const mockMixin = { + methods: { + getCmsContent: jest.fn((widget, _) => (widget === widgetName ? descriptions : [])) + } + }; + mountOptions.mixins = [mockMixin]; + const storeData = { + order: { + lineItems: { + vaps: vapsInOrder + } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result).toBe(null); + }); + test('returns item with expected price when part type found in vapsInOrder', () => { + // Arrange + const mountOptions = getMountOptions({ + router: { navigate: jest.fn() } + }); + const part = 'some part'; + const vaps = [{ partType: part }]; + const storeData = { + order: { + lineItems: { vaps } + } + }; + const testingPinia = createTestingPinia({ + initialState: { + main: storeData + } + }); + useMainStore(testingPinia); + mountOptions.global.plugins = [testingPinia]; + const expectedSubtotal = 93; + getPriceOfLineItems.mockImplementation(() => expectedSubtotal); + const wrapper = shallowMount(cartDropdown, mountOptions); + + // Act + const result = wrapper.vm.getCartItemForVapsPart(part); + + // Assert + expect(result.subTotal).toBe(expectedSubtotal); }); }); - describe('Mobile Fee', () => { - test('Shows mobile fee when preset', () => { - // Arrange - const reference = '#mobile-fee-item'; - const { wrapper } = getMountedComponent({ - order: { - lineItems: { - supportingItems: [], - mobileFee: {} - } - } - }); - - // Act - const cartDeductible = wrapper.find(reference); - - // Assert - expect(wrapper.vm.mobileFeeCartItem).not.toBeNull(); - expect(cartDeductible.exists()).toBeTruthy(); - }); - test('Hides recycle fee when not in supported items', () => { - // Arrange - const reference = '#mobile-fee-item'; - const { wrapper } = getMountedComponent({ - order: { - lineItems: { - supportingItems: [], - mobileFee: null - } - } - }); - - // Act - const cartDeductible = wrapper.find(reference); - - // Assert - expect(wrapper.vm.mobileFeeCartItem).toBeNull(); - expect(cartDeductible.exists()).toBeFalsy(); - }); + describe('removeItem', () => { + // TODO when written }); }); }); diff --git a/src/iss-components/cart-dropdown/cart-dropdown.vue b/src/iss-components/cart-dropdown/cart-dropdown.vue index c4dab0dd..e6e09442 100644 --- a/src/iss-components/cart-dropdown/cart-dropdown.vue +++ b/src/iss-components/cart-dropdown/cart-dropdown.vue @@ -23,7 +23,7 @@ id="cart-deductible-or-base-price" class="cart-item cart-gray">
{{ deductibleLabel }} @@ -39,35 +39,69 @@
+
+
+ + {{ formatAmountInDollars(packagePrice) }} +
+
+
+
+ {{ item?.name ?? '' }} + + + +
+
+
+
+ class="cart-item">
- {{ mobileFeeCartItem.name }} + {{ mobileFeeCartItem?.name ?? '' }} {{ getDisplayed(mobileFeeCartItem.subTotal) }}
+ class="cart-item cart-gray">
+ linkType="text" + :text="recycleFeeCartItem?.name ?? ''" + href="javascript:void(0)" + @clickEvent="openModal(recyclingModalCmsWidgetName)" /> {{ getDisplayed(recycleFeeCartItem.subTotal) }}
@@ -102,7 +136,7 @@
+ :cmsWidgetName="recyclingModalCmsWidgetName">
@@ -116,36 +150,43 @@ import textBlock from '@/digital-components/text-block/text-block.vue'; import textLink from '@/ux-components/text-link/text-link.vue'; import getPriceOfLineItems from '@/helpers/price-calculator.js'; import { formatAmountInDollars } from '@/helpers/text-helper.js'; -import baseMixin from '@/mixins/base-mixin'; -import partTypeStrings from '@/constants/part-type-strings'; +import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/service-package-helper.js'; + +// Constants +import partTypeStrings from '@/constants/part-type-strings.js'; +import partNumberStrings from '@/constants/part-number-strings.js'; import widgetFields from '@/constants/cms-widget-fields.js'; const VERIFYING_COVERAGE = 'Verifying coverage'; export default { name: 'cart-dropdown', - components: { contentGroupModal, + components: { + contentGroupModal, textBlock, - textLink }, - props: { - recyclingModalCmsWidgetName: String, - showAsPaid: Boolean + textLink }, - setup() { - const mainStore = useMainStore(); - return { mainStore }; + props: { + showAsPaid: Boolean, + readOnly: Boolean, + recyclingModalCmsWidgetName: String, + availableVaps: Array }, data() { const { currentDeductible } = useMainStore().order; - const { supportingItems, glassParts, otherParts } = useMainStore().lineItems; - const baseServiceLineItems = [ - ...(supportingItems ?? []), - ...(glassParts ?? []), - ...(otherParts ?? []) - ]; + const { glassToReplace, isRepair } = useMainStore().damage; + const { supportingItems, glassParts, otherParts, vaps } = useMainStore().lineItems; + return { + isRepair, + glassToReplace: glassToReplace ?? [], + vapsInOrder: vaps ?? [], + baseServiceLineItems: [ + ...(supportingItems ?? []), + ...(glassParts ?? []), + ...(otherParts ?? []) + ], deductible: currentDeductible, - baseServiceLineItems, isExpanded: false, widget: { amountDue: 'AmountDueTextWidget', @@ -154,24 +195,28 @@ export default { subtotal: 'SubtotalWidget', salesTax: 'SalesTaxWidget', recycleFee: 'RecycleFeeWidget', - mobileFee: 'MobileServiceWidget' + mobileFee: 'MobileServiceWidget', + servicePackage: 'ServicePackageTitle', + vapsItemDescriptions: 'VapsItemDescriptions' } }; }, computed: { + showDeductibleCartItem() { + return !useMainStore().isNoComp && !useMainStore().policy.isITAC; + }, baseServicePrice() { return getPriceOfLineItems(this.baseServiceLineItems); }, - showDeductibleLineItem() { - return !useMainStore().isNoComp && !useMainStore().policy.isITAC; - }, isUnverified() { - return this.showDeductibleLineItem + return !useMainStore().isNoComp && !useMainStore().policy.isITAC && (this.deductible == null || !useMainStore().isVerifiedCoverageStatus); }, subTotal() { - // TODO partial calculation for now - return this.showDeductibleLineItem ? this.deductible : this.baseServicePrice; + const basePrice = !useMainStore().isNoComp && !useMainStore().policy.isITAC + ? this.deductible + : this.baseServicePrice; + return basePrice + this.packagePrice; }, salesTax() { return 0; // TODO @@ -181,6 +226,53 @@ export default { ? 0 : this.subTotal + this.salesTax; }, + availableLineItems() { + const { glassParts, supportingItems } = useMainStore().lineItems; + return [ + ...(glassParts ?? []), + ...(supportingItems ?? []), + ...(this.availableVaps ?? []) + ]; + }, + servicePackageTier() { + return getHighestFullySatisfiedTier( + this.glassToReplace, + this.availableLineItems, + this.isRepair, + this.vapsInOrder + ); + }, + servicePackageCartItems() { + const packageContentTypes = getPackageContents( + this.glassToReplace, + this.availableLineItems, + this.isRepair, + this.servicePackageTier + ) ?? []; + const items = []; + if (packageContentTypes.includes(partTypeStrings.FRONT_WIPER) ?? this.frontWipersCartItem) { + items.push(this.frontWipersCartItem); + } + if (packageContentTypes.includes(partTypeStrings.REAR_WIPER) ?? this.rearWipersCartItem) { + items.push(this.rearWipersCartItem); + } + if (packageContentTypes.includes(partTypeStrings.RAIN_DEFENSE) ?? this.rainDefenseCartItem) { + items.push(this.rainDefenseCartItem); + } + return items; + }, + frontWipersCartItem() { + return this.getCartItemForVapsPart(partTypeStrings.FRONT_WIPER); + }, + rearWipersCartItem() { + return this.getCartItemForVapsPart(partTypeStrings.REAR_WIPER); + }, + rainDefenseCartItem() { + return this.getCartItemForVapsPart(partTypeStrings.RAIN_DEFENSE); + }, + packagePrice() { + return getPriceOfLineItems(this.vapsInOrder); + }, amountDueLabel() { return this.getCmsContent(this.widget.amountDue, widgetFields.TEXT_BLOCK_WIDGET.TEXT); }, @@ -196,17 +288,39 @@ export default { salesTaxLabel() { return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT); }, - recycleFeeCartItem() { - return this.getCartItem( - this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT), - this.mainStore.order.lineItems.supportingItems?.find((lineItem) => lineItem.partType === partTypeStrings.REPLACE_FEE) + servicePackageNames() { + return this.getCmsContent( + this.widget.servicePackage, + widgetFields.INPUT_QUESTION_WIDGET.ANSWERS ); }, + servicePackageLabelWidget() { + if (!this.servicePackageNames) { + return ''; + } + const currentPackage = this.servicePackageNames + ?.find((entry) => entry?.Name === this.servicePackageTier); + + return currentPackage?.SubWidgetName ?? ''; + }, + recycleFeeCartItem() { + const recycleFeeLineItem = useMainStore().lineItems.supportingItems + ?.find((lineItem) => lineItem.partNumber === partNumberStrings.RECYCLE_FEE); + return recycleFeeLineItem + ? this.getCartItem( + this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT), + [recycleFeeLineItem] + ) + : null; + }, mobileFeeCartItem() { - return this.getCartItem( - this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT), - this.mainStore.order.lineItems.mobileFee - ); + const mobileFeeLineItem = useMainStore().lineItems.mobileFee; + return mobileFeeLineItem + ? this.getCartItem( + this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT), + [mobileFeeLineItem] + ) + : null; } }, methods: { @@ -217,23 +331,50 @@ export default { this.isExpanded = !this.isExpanded; }, getDisplayed(amount) { - return this.isUnverified && this.showDeductibleLineItem + return this.isUnverified + && !useMainStore().isNoComp + && !useMainStore().policy.isITAC ? VERIFYING_COVERAGE : formatAmountInDollars(amount); }, - getCartItem(label, lineItem) { + getCartItem(label, lineItems) { let cartItem = null; - if (lineItem) { + if (lineItems && lineItems.length > 0) { cartItem = { name: label, - subTotal: baseMixin.methods.getTotalLineItemPrice(lineItem, false), - salesTax: lineItem.salesTax ?? 0 + subTotal: getPriceOfLineItems(lineItems), + salesTax: lineItems.reduce( + (accumulator, lineItem) => accumulator + lineItem.salesTax, + 0 + ) }; } return cartItem; }, + getCartItemForVapsPart(partType) { + const label = this.getCmsContentForVapsType(partType); + const lineItems = this.vapsInOrder + ?.filter((vapsLineItem) => vapsLineItem.partType === partType); + return this.getCartItem(label, lineItems); + }, + removeItem() { + // TODO in later ticket + }, + getCmsContentForVapsType(vapsType) { + const vapsItemDescriptions = this.getCmsContent( + this.widget.vapsItemDescriptions, + widgetFields.INPUT_QUESTION_WIDGET.ANSWERS + ); + + if (!vapsItemDescriptions) { + return ''; + } + + const vapsTypeDescription = vapsItemDescriptions?.find((entry) => entry?.Name === vapsType); + return vapsTypeDescription?.Text ?? ''; + }, formatAmountInDollars } }; @@ -242,6 +383,10 @@ export default {