Merge branch 'develop' into feature/humphries/INSR-7768
This commit is contained in:
commit
67faf453ea
37 changed files with 5451 additions and 1898 deletions
|
|
@ -8,9 +8,18 @@ schedules:
|
||||||
- develop
|
- develop
|
||||||
pool: 'Default'
|
pool: 'Default'
|
||||||
|
|
||||||
|
resources:
|
||||||
|
repositories:
|
||||||
|
- repository: AzureDevOps
|
||||||
|
type: github
|
||||||
|
name: Safelite/AzureDevOps
|
||||||
|
endpoint: Safelite
|
||||||
|
ref: refs/tags/t5.7.40
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# - group: Digital-Infrastructure
|
# - group: Digital-Infrastructure
|
||||||
# - group: ISS-BuildBranches
|
# - group: ISS-BuildBranches
|
||||||
|
- group: SafelitePlaywright
|
||||||
- name: dockerImageName
|
- name: dockerImageName
|
||||||
value: 'playwright-tests'
|
value: 'playwright-tests'
|
||||||
- name: imageTag
|
- name: imageTag
|
||||||
|
|
@ -30,158 +39,17 @@ stages:
|
||||||
- stage: TestPr
|
- stage: TestPr
|
||||||
displayName: Run Playwright Test
|
displayName: Run Playwright Test
|
||||||
jobs:
|
jobs:
|
||||||
- job: playwright_tests
|
- template: templates/digital/playwright-test.yml@AzureDevOps
|
||||||
continueOnError: true
|
parameters:
|
||||||
strategy:
|
applicationType: 'vue'
|
||||||
matrix:
|
totalShards: ${{ variables.totalShards }}
|
||||||
shard1:
|
targetUrl: $(BASE_URL)
|
||||||
shardNumber: 1
|
dockerFileName: 'Dockerfile.playwright'
|
||||||
shard2:
|
isRegression: ${{ variables.IS_REGRESSION }}
|
||||||
shardNumber: 2
|
filterTags: '@Advanced'
|
||||||
shard3:
|
playwrightTestsPath: 'playwright-tests'
|
||||||
shardNumber: 3
|
npmServePath: '.'
|
||||||
shard4:
|
npmrcPath: 'playwright-tests/.npmrc'
|
||||||
shardNumber: 4
|
secrets:
|
||||||
|
CCIS_API_AUTH: $(CCIS_API_AUTH)
|
||||||
steps:
|
JIRA_API_KEY: $(JIRA_API_KEY)
|
||||||
- task: Docker@2
|
|
||||||
displayName: 'Build Docker Image'
|
|
||||||
inputs:
|
|
||||||
command: build
|
|
||||||
dockerfile: Dockerfile.playwright
|
|
||||||
repository: $(dockerImageName)
|
|
||||||
tags: $(imageTag)
|
|
||||||
arguments: '--no-cache --pull'
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
# Create container and run tests
|
|
||||||
container_id=$(docker create \
|
|
||||||
--ipc=host \
|
|
||||||
-e CCIS_API_AUTH=$(CCIS_API_AUTH) \
|
|
||||||
-e BASE_URL=$(BASE_URL) \
|
|
||||||
-e CCIS_API_URL=$(CCIS_API_URL) \
|
|
||||||
-e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \
|
|
||||||
-e SHARD=$(shardNumber) \
|
|
||||||
-e CI=true \
|
|
||||||
-e NODE_ENV=$(NODE_ENV) \
|
|
||||||
$(dockerImageName):$(imageTag) \
|
|
||||||
npx concurrently -k -n "server,playwright"\
|
|
||||||
"sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\
|
|
||||||
"npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@smoke|@Advanced\"")
|
|
||||||
|
|
||||||
# Start container and stream logs
|
|
||||||
echo "Starting tests for shard $(shardNumber)..."
|
|
||||||
docker start -a $container_id
|
|
||||||
|
|
||||||
# Create directory for test results
|
|
||||||
echo "Creating test results directory..."
|
|
||||||
mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)
|
|
||||||
|
|
||||||
# Copy test results from container
|
|
||||||
echo "Copying test results..."
|
|
||||||
docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/
|
|
||||||
|
|
||||||
# Remove container
|
|
||||||
echo "Cleaning up container..."
|
|
||||||
docker rm $container_id
|
|
||||||
|
|
||||||
# Check if tests failed
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Tests failed in shard $(shardNumber) or tests don't exist for this shard number!"
|
|
||||||
exit 0 # Suppress error. It will be visible in report.
|
|
||||||
fi
|
|
||||||
displayName: 'Run Playwright Tests - Shard $(shardNumber)'
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@1
|
|
||||||
displayName: 'Publish Test Reports - Shard $(shardNumber)'
|
|
||||||
condition: always()
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)'
|
|
||||||
artifact: 'playwright-report-shard-$(shardNumber)'
|
|
||||||
publishLocation: 'pipeline'
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
docker rmi $(dockerImageName):$(imageTag) -f
|
|
||||||
displayName: 'Cleanup Docker Image'
|
|
||||||
condition: always()
|
|
||||||
|
|
||||||
- job: download_and_merge_reports
|
|
||||||
dependsOn: playwright_tests
|
|
||||||
timeoutInMinutes: 8
|
|
||||||
cancelTimeoutInMinutes: 10
|
|
||||||
steps:
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports'
|
|
||||||
- task: Docker@2
|
|
||||||
displayName: 'Build Docker Image'
|
|
||||||
inputs:
|
|
||||||
command: build
|
|
||||||
dockerfile: Dockerfile.playwright
|
|
||||||
repository: $(dockerImageName)
|
|
||||||
tags: $(imageTag)
|
|
||||||
arguments: '--no-cache --pull'
|
|
||||||
- bash: |
|
|
||||||
# Create container for jira writeback
|
|
||||||
container_id=$(docker create \
|
|
||||||
--ipc=host \
|
|
||||||
-e JIRA_SERVER=$(JIRA_SERVER) \
|
|
||||||
-e JIRA_USERNAME=$(JIRA_USERNAME) \
|
|
||||||
-e JIRA_API_KEY=$(JIRA_API_KEY) \
|
|
||||||
-e IS_REGRESSION=$(IS_REGRESSION) \
|
|
||||||
-e JIRA_BOARD_ID=$(JIRA_BOARD_ID) \
|
|
||||||
-e JIRA_EPIC_KEY=$(JIRA_EPIC_KEY) \
|
|
||||||
-e JIRA_PROJECT_KEY=$(JIRA_PROJECT_KEY) \
|
|
||||||
$(dockerImageName):$(imageTag) \
|
|
||||||
bash -c "echo \"Moving Playwright reports out of subfolders...\" &&
|
|
||||||
find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \; &&
|
|
||||||
echo \"Merging reports...\" &&
|
|
||||||
PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter='playwright-tests/impl/reporter/JiraWritebackReporter.ts',junit ./playwright-reports
|
|
||||||
echo \"Contents of ortoni-report:\" && ls ./ortoni-report &&
|
|
||||||
echo 'Current dir: ' && pwd
|
|
||||||
echo 'Contents of current dir: ' && ls
|
|
||||||
echo 'Contents of /app/test-results' && ls /app/test-results ")
|
|
||||||
|
|
||||||
# Start container and stream logs
|
|
||||||
echo "Starting merge"
|
|
||||||
docker start -a $container_id
|
|
||||||
|
|
||||||
# Create directory for test results
|
|
||||||
echo "Creating test results directory..."
|
|
||||||
mkdir -p $(System.DefaultWorkingDirectory)/ortoni-report
|
|
||||||
mkdir -p $(System.DefaultWorkingDirectory)/test-results
|
|
||||||
|
|
||||||
# Copy test results from container
|
|
||||||
echo "Copying test results..."
|
|
||||||
docker cp $container_id:/app/ortoni-report/. $(System.DefaultWorkingDirectory)/ortoni-report
|
|
||||||
docker cp $container_id:/app/test-results/junit_results.xml $(System.DefaultWorkingDirectory)/test-results
|
|
||||||
|
|
||||||
# Remove container
|
|
||||||
echo "Cleaning up container..."
|
|
||||||
docker rm $container_id
|
|
||||||
env:
|
|
||||||
JIRA_API_KEY: $(JIRA_API_KEY)
|
|
||||||
displayName: merge_and_publish_results_to_jira
|
|
||||||
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
displayName: 'Publish test results'
|
|
||||||
inputs:
|
|
||||||
searchFolder: 'test-results'
|
|
||||||
testResultsFormat: 'JUnit'
|
|
||||||
testResultsFiles: 'junit_results.xml'
|
|
||||||
mergeTestResults: true
|
|
||||||
failTaskOnFailedTests: false
|
|
||||||
testRunTitle: 'Playwright Tests'
|
|
||||||
condition: succeededOrFailed()
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@1
|
|
||||||
displayName: 'Publish Merged Report'
|
|
||||||
condition: always()
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report'
|
|
||||||
artifact: 'playwright-merged-report'
|
|
||||||
publishLocation: 'pipeline'
|
|
||||||
- script: |
|
|
||||||
docker rmi $(dockerImageName):$(imageTag) -f
|
|
||||||
displayName: 'Cleanup Docker Image'
|
|
||||||
condition: always()
|
|
||||||
3
playwright-tests/.npmrc
Normal file
3
playwright-tests/.npmrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
registry=https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/
|
||||||
|
|
||||||
|
always-auth=true
|
||||||
4013
playwright-tests/package-lock.json
generated
Normal file
4013
playwright-tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
playwright-tests/package.json
Normal file
37
playwright-tests/package.json
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "safelite-iss-nextgen-playwright-tests",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Playwright tests for ISS Nextgen using Safelite Playwright Core",
|
||||||
|
"directories": {
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "playwright test",
|
||||||
|
"test:headed": "playwright test --headed",
|
||||||
|
"test:debug": "playwright test --debug",
|
||||||
|
"test:smoke": "playwright test --grep @smoke",
|
||||||
|
"test:report": "playwright test --grep @test_report",
|
||||||
|
"report": "playwright show-report",
|
||||||
|
"install": "playwright install",
|
||||||
|
"install:deps": "playwright install-deps"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
|
"safelite-playwright-core": "^1.0.26"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^9.8.0",
|
||||||
|
"@playwright/test": "^1.52.0",
|
||||||
|
"@types/dotenv-safe": "^8.1.6",
|
||||||
|
"@types/node": "^22.15.32",
|
||||||
|
"dotenv-safe": "^9.1.0",
|
||||||
|
"eslint": "^9.28.0",
|
||||||
|
"luxon": "^3.6.1",
|
||||||
|
"ortoni-report": "^3.0.5",
|
||||||
|
"playwright-jira-reporter": "^1.0.16"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
|
|
@ -1,42 +1,61 @@
|
||||||
|
import { formatDateForFilename } from 'safelite-playwright-core';
|
||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { JiraReporterConfig } from 'playwright-jira-reporter'
|
||||||
import dotenv from 'dotenv-safe';
|
import dotenv from 'dotenv-safe';
|
||||||
import { OrtoniReportConfig } from "ortoni-report";
|
import { OrtoniReportConfig } from "ortoni-report";
|
||||||
import * as path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
if (!process.env.CI) {
|
if (!process.env.CI) {
|
||||||
// Environment variables are present in CI environment, no need to read from file
|
// Environment variables are present in CI environment, no need to read from file
|
||||||
if (process.env.NODE_ENV == 'undefined' || process.env.NODE_ENV == null) {
|
const basePath = __dirname; // This gets the directory where the config file is located
|
||||||
dotenv.config({ path: `playwright-tests/.env.dev`, example: 'playwright-tests/.env.example' });
|
|
||||||
}
|
if (process.env.PLAYWRIGHT_ENV == undefined || process.env.PLAYWRIGHT_ENV == null) {
|
||||||
else {
|
dotenv.config({
|
||||||
dotenv.config({ path: `playwright-tests/.env.${process.env.NODE_ENV}`, example: 'playwright-tests/.env.example' });
|
path: path.join(basePath, '.env.dev'),
|
||||||
|
example: path.join(basePath, '.env.example')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dotenv.config({
|
||||||
|
path: path.join(basePath, `.env.${process.env.PLAYWRIGHT_ENV}`),
|
||||||
|
example: path.join(basePath, '.env.example')
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ortoni config
|
||||||
|
const ortoniReportConfig: OrtoniReportConfig = {
|
||||||
|
open: "never",
|
||||||
|
folderPath: process.env.CI ? 'ortoni-report' : 'test-results',
|
||||||
|
title: "ISS-NextGen Test Report",
|
||||||
|
filename: `iss_nextgen_ortoni_report_${formatDateForFilename(new Date())}.html`,
|
||||||
|
showProject: false,
|
||||||
|
projectName: "ISS-NextGen-Playwright-Report",
|
||||||
|
testType: `E2E- Environment: ${process.env.PLAYWRIGHT_ENV} `,
|
||||||
|
preferredTheme: "light",
|
||||||
|
base64Image: true,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// Jira Report config
|
||||||
* Read environment variables from file.
|
const jiraReportConfig: JiraReporterConfig = {
|
||||||
* https://github.com/motdotla/dotenv
|
// Jira Reporter Config
|
||||||
*/
|
isRegressionRun: process.env.IS_REGRESSION === 'true',
|
||||||
// import dotenv from 'dotenv';
|
jiraProjectKey: process.env.JIRA_PROJECT_KEY || '',
|
||||||
// import path from 'path';
|
jiraEpicKey: process.env.JIRA_EPIC_KEY || '',
|
||||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
jiraCardNumber: process.env.JIRA_CARD_NUMBER || '',
|
||||||
|
applicationName: 'ISS NextGen',
|
||||||
/**
|
jiraApiUtilConfig: {
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
jiraUrl: process.env.JIRA_SERVER || '',
|
||||||
*/
|
jiraUsername: process.env.JIRA_USERNAME || '',
|
||||||
const reportConfig: OrtoniReportConfig = {
|
jiraApiKey: process.env.JIRA_API_KEY || '',
|
||||||
port: 1994,
|
jiraBoardId: process.env.JIRA_BOARD_ID || ''
|
||||||
open: "never",
|
},
|
||||||
folderPath: "test-results",
|
jiraCreationPermissions: {
|
||||||
filename: "index.html",
|
isCreateTestSubtasks: process.env.IS_REGRESSION !== 'true',
|
||||||
logo: "../business-logic/data/logo.png",
|
isCreateBugs: process.env.IS_REGRESSION !== 'true'
|
||||||
title: "Test Report",
|
},
|
||||||
showProject: false,
|
// Wrapped ortoni config
|
||||||
projectName: "ISS-Nextgen-Playwright-Report",
|
...ortoniReportConfig
|
||||||
testType: `E2E- Environment: ${process.env.NODE_ENV} `,
|
|
||||||
preferredTheme: "light",
|
|
||||||
base64Image: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reportFilePath = path.resolve(__dirname, './../test-results/accessibility-report.html');
|
export const reportFilePath = path.resolve(__dirname, './../test-results/accessibility-report.html');
|
||||||
|
|
@ -53,12 +72,16 @@ export default defineConfig({
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 4 : 5,
|
workers: process.env.CI ? 4 : 5,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: process.env.CI? [
|
||||||
['ortoni-report', reportConfig],
|
['junit'],
|
||||||
|
['playwright-jira-reporter', jiraReportConfig],
|
||||||
|
['ortoni-report', ortoniReportConfig]
|
||||||
|
]: [
|
||||||
|
['ortoni-report', ortoniReportConfig],
|
||||||
['junit'],
|
['junit'],
|
||||||
['list']
|
['list']
|
||||||
],
|
],
|
||||||
timeout: 120_000,
|
timeout: 240_000,
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
|
@ -68,52 +91,15 @@ export default defineConfig({
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
headless: process.env.CI ? true : false,
|
headless: process.env.CI ? true : false,
|
||||||
screenshot: "only-on-failure",
|
screenshot: "only-on-failure",
|
||||||
actionTimeout: 5_000,
|
actionTimeout: 60_000,
|
||||||
navigationTimeout: 20_000
|
navigationTimeout: 60_000
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices['Desktop Chrome'] },
|
||||||
},
|
},
|
||||||
|
],
|
||||||
// {
|
});
|
||||||
// name: 'firefox',
|
|
||||||
// use: { ...devices['Desktop Firefox'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
// {
|
|
||||||
// name: 'webkit',
|
|
||||||
// use: { ...devices['Desktop Safari'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Chrome',
|
|
||||||
// use: { ...devices['Pixel 5'] },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Safari',
|
|
||||||
// use: { ...devices['iPhone 12'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
// webServer: {
|
|
||||||
// command: 'npm run start',
|
|
||||||
// url: 'http://127.0.0.1:3000',
|
|
||||||
// reuseExistingServer: !process.env.CI,
|
|
||||||
// },
|
|
||||||
});
|
|
||||||
BIN
src/assets/img/icons/icon-ablue-next.png
Normal file
BIN
src/assets/img/icons/icon-ablue-next.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 B |
BIN
src/assets/img/icons/icon-ablue-previous.png
Normal file
BIN
src/assets/img/icons/icon-ablue-previous.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 510 B |
|
|
@ -139,7 +139,7 @@ const endpoints = Object.freeze({
|
||||||
},
|
},
|
||||||
TaxOrderItems: {
|
TaxOrderItems: {
|
||||||
url: `${PRICE_BASE_URL}/taxed-order-items`,
|
url: `${PRICE_BASE_URL}/taxed-order-items`,
|
||||||
method: 'GET'
|
method: 'POST'
|
||||||
},
|
},
|
||||||
LogExperimentExposureIfAssigned: {
|
LogExperimentExposureIfAssigned: {
|
||||||
url: `${EXPERIMENTS_BASE_URL}/log-exposure`,
|
url: `${EXPERIMENTS_BASE_URL}/log-exposure`,
|
||||||
|
|
@ -217,7 +217,6 @@ const endpoints = Object.freeze({
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
},
|
},
|
||||||
DuplicateSearch: {
|
DuplicateSearch: {
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
url: `${ORDER_BASE_URL}/duplicate-check`,
|
url: `${ORDER_BASE_URL}/duplicate-check`,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
},
|
},
|
||||||
|
|
@ -231,4 +230,4 @@ const endpoints = Object.freeze({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export { endpoints };
|
export default endpoints;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const errorMessages = Object.freeze({
|
||||||
VIN_FORMAT:
|
VIN_FORMAT:
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
|
'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
|
||||||
OPTION_REQUIRED: 'Please select an option',
|
OPTION_REQUIRED: 'Please choose an option',
|
||||||
VEHICLE_REQUIRED: 'Please select a vehicle',
|
VEHICLE_REQUIRED: 'Please select a vehicle',
|
||||||
POLICY_NUMBER_REQUIRED: 'Policy number is required.',
|
POLICY_NUMBER_REQUIRED: 'Policy number is required.',
|
||||||
POLICY_NUMBER_FORMAT: 'Please enter an alpha-numeric string',
|
POLICY_NUMBER_FORMAT: 'Please enter an alpha-numeric string',
|
||||||
|
|
@ -57,13 +57,15 @@ const errorMessages = Object.freeze({
|
||||||
POLICYHOLDER_FIRST_NAME_REQUIRED: 'First name is required.',
|
POLICYHOLDER_FIRST_NAME_REQUIRED: 'First name is required.',
|
||||||
POLICYHOLDER_LAST_NAME_REQUIRED: 'Last name is required.',
|
POLICYHOLDER_LAST_NAME_REQUIRED: 'Last name is required.',
|
||||||
ACKNOWLEDGEMENT_REQUIRED: 'You must agree to the terms to continue',
|
ACKNOWLEDGEMENT_REQUIRED: 'You must agree to the terms to continue',
|
||||||
|
NO_SELECTION_REQUIRED: 'Please make a selection to continue',
|
||||||
|
|
||||||
YEAR_REQUIRED: 'Vehicle year is required.',
|
YEAR_REQUIRED: 'Vehicle year is required.',
|
||||||
MAKE_REQUIRED: 'Vehicle make is required.',
|
MAKE_REQUIRED: 'Vehicle make is required.',
|
||||||
MODEL_REQUIRED: 'Vehicle model is required.',
|
MODEL_REQUIRED: 'Vehicle model is required.',
|
||||||
STYLE_REQUIRED: 'Vehicle style is required.',
|
STYLE_REQUIRED: 'Vehicle style is required.',
|
||||||
MOBILE_LOCATION_REQUIRED: 'Please enter your service address',
|
MOBILE_LOCATION_REQUIRED: 'Please enter your service address',
|
||||||
DATE_REQUIRED: 'Please select a date'
|
DATE_REQUIRED: 'Please select a date',
|
||||||
|
TIME_REQUIRED: 'Please select an appointment time.'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default errorMessages;
|
export default errorMessages;
|
||||||
|
|
|
||||||
6
src/constants/provider-preference.js
Normal file
6
src/constants/provider-preference.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
const PROVIDER_PREFERENCE_OPTIONS = Object.freeze({
|
||||||
|
SAFELITE: 'SafeliteOption',
|
||||||
|
TPA: 'TPAOption'
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PROVIDER_PREFERENCE_OPTIONS;
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -17,6 +17,21 @@ export function convertDateToDateString(date) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertDateToTwoDigitDay(date) {
|
||||||
|
if (date instanceof Date !== true) return null;
|
||||||
|
return (`0${date.getDate()}`).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertDateToTwoDigitMonth(date) {
|
||||||
|
if (date instanceof Date !== true) return null;
|
||||||
|
return (`0${date.getMonth() + 1}`).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertDateToShortMonth(date) {
|
||||||
|
if (date instanceof Date !== true) return null;
|
||||||
|
return date.toLocaleString('en-US', { month: 'short' });
|
||||||
|
}
|
||||||
|
|
||||||
export function convertDateStringToDate(dateString) {
|
export function convertDateStringToDate(dateString) {
|
||||||
// dateString must be YYYY-MM-DD format
|
// dateString must be YYYY-MM-DD format
|
||||||
if (typeof dateString !== 'string') return null;
|
if (typeof dateString !== 'string') return null;
|
||||||
|
|
@ -51,6 +66,12 @@ export function getDisplayTextForDurationLength(durationMinimum, durationMaximum
|
||||||
return `${durationText} ${unitText}`;
|
return `${durationText} ${unitText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAfternoon(timeString) {
|
||||||
|
if (typeof timeString !== 'string') return false;
|
||||||
|
const hours = parseInt(timeString.split(':')[0], 10);
|
||||||
|
return hours >= 12;
|
||||||
|
}
|
||||||
|
|
||||||
export function militaryToTwelveHourTime(timeString) {
|
export function militaryToTwelveHourTime(timeString) {
|
||||||
// Expected input: "HH:MM"
|
// Expected input: "HH:MM"
|
||||||
if (typeof timeString !== 'string') return null;
|
if (typeof timeString !== 'string') return null;
|
||||||
|
|
@ -163,9 +184,11 @@ export function combineDateAndTime(date, time) {
|
||||||
// Return the new date object
|
// Return the new date object
|
||||||
return newDate;
|
return newDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addMinutes(date, minutes) {
|
export function addMinutes(date, minutes) {
|
||||||
return new Date(date.getTime() + minutes * 60000);
|
return new Date(date.getTime() + minutes * 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shortTimeString(date) {
|
export function shortTimeString(date) {
|
||||||
// Use a ternary operator to check if the input is a valid date object
|
// Use a ternary operator to check if the input is a valid date object
|
||||||
return date instanceof Date
|
return date instanceof Date
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<a
|
<a
|
||||||
href=""
|
href=""
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@click="openCookiePreferences">Cookie Preferences</a>
|
@click="openCookiePreferences">Cookie preferences</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-menu-item">
|
<div class="footer-menu-item">
|
||||||
<textLink
|
<textLink
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<baseInputButton
|
<baseInputButton
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover">
|
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2">
|
||||||
<div
|
<div
|
||||||
:aria-label="buttonLabel"
|
:aria-label="buttonLabel"
|
||||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
|
|
@ -121,6 +121,7 @@ $heritage-checked-border-color: #0070d1;
|
||||||
&:checked + .list-button-content {
|
&:checked + .list-button-content {
|
||||||
background: $heritage-checked-background-color;
|
background: $heritage-checked-background-color;
|
||||||
border-color: $heritage-checked-border-color;
|
border-color: $heritage-checked-border-color;
|
||||||
|
box-shadow: 0 0 0 1px $blue;
|
||||||
.button-label-copy {
|
.button-label-copy {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $black;
|
color: $black;
|
||||||
|
|
@ -142,8 +143,6 @@ $heritage-checked-border-color: #0070d1;
|
||||||
box-shadow: $heritage-box-shadow;
|
box-shadow: $heritage-box-shadow;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin-bottom: .625rem;
|
|
||||||
height: 4.375rem;
|
|
||||||
}
|
}
|
||||||
.button-content {
|
.button-content {
|
||||||
row-gap: 0.25rem;
|
row-gap: 0.25rem;
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ export default {
|
||||||
VehiclesForQuestions() {
|
VehiclesForQuestions() {
|
||||||
// Map API result data, to address-vehicles data structure
|
// Map API result data, to address-vehicles data structure
|
||||||
const mappedData = this.VehiclesFromApi.map((v) => {
|
const mappedData = this.VehiclesFromApi.map((v) => {
|
||||||
const maskSymbol = 'X';
|
const maskSymbol = '*';
|
||||||
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
||||||
const vinEnd = v.vin.substring(v.vin.length - 6);
|
const vinEnd = v.vin.substring(v.vin.length - 6);
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRu
|
||||||
return { wrapper };
|
return { wrapper };
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateOrderText = 'Finish Existing Claim';
|
const duplicateOrderText = 'Finish existing claim';
|
||||||
|
|
||||||
describe('duplicateCheck.vue', () => {
|
describe('duplicateCheck.vue', () => {
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
|
|
@ -259,81 +259,6 @@ describe('duplicateCheck.vue', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getNewOrderSelectionName', () => {
|
|
||||||
test('answersFromCms null => returns empty string', () => {
|
|
||||||
// Arrange
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
router: { navigate: jest.fn() }
|
|
||||||
});
|
|
||||||
const answersFromCmsValue = null;
|
|
||||||
mountOptions.mixins = [{
|
|
||||||
methods: {
|
|
||||||
getCmsContent: jest.fn().mockImplementation((_, label) => (label === 'Answers' ? answersFromCmsValue : ''))
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
const wrapper = shallowMount(duplicateCheck, mountOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.getNewOrderSelectionName).toBe('');
|
|
||||||
});
|
|
||||||
test('answersFromCms empty list => returns empty string', () => {
|
|
||||||
// Arrange
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
router: { navigate: jest.fn() }
|
|
||||||
});
|
|
||||||
const answersFromCmsValue = [];
|
|
||||||
mountOptions.mixins = [{
|
|
||||||
methods: {
|
|
||||||
getCmsContent: jest.fn().mockImplementation((_, label) => (label === 'Answers' ? answersFromCmsValue : ''))
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
const wrapper = shallowMount(duplicateCheck, mountOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.getNewOrderSelectionName).toBe('');
|
|
||||||
});
|
|
||||||
test('answersFromCms non-empty list whose first item has no Name property => returns empty string', () => {
|
|
||||||
// Arrange
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
router: { navigate: jest.fn() }
|
|
||||||
});
|
|
||||||
const answersFromCmsValue = [{ test: getRandomString(6, 6) }];
|
|
||||||
mountOptions.mixins = [{
|
|
||||||
methods: {
|
|
||||||
getCmsContent: jest.fn().mockImplementation((_, label) => (label === 'Answers'
|
|
||||||
? answersFromCmsValue
|
|
||||||
: ''))
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
const wrapper = shallowMount(duplicateCheck, mountOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.getNewOrderSelectionName).toBe('');
|
|
||||||
});
|
|
||||||
test('answersFromCms first item has Name property => returns expected', () => {
|
|
||||||
// Arrange
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
router: { navigate: jest.fn() }
|
|
||||||
});
|
|
||||||
const expectedName = getRandomString(6, 6);
|
|
||||||
const answersFromCmsValue = [{ Name: expectedName }];
|
|
||||||
mountOptions.mixins = [{
|
|
||||||
methods: {
|
|
||||||
getCmsContent: jest.fn().mockImplementation((_, label) => (label === 'Answers'
|
|
||||||
? answersFromCmsValue
|
|
||||||
: ''))
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
|
|
||||||
const wrapper = shallowMount(duplicateCheck, mountOptions);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.getNewOrderSelectionName).toBe(expectedName);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Navigation', () => {
|
describe('Navigation', () => {
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,16 @@
|
||||||
class="duplicate-check-question"
|
class="duplicate-check-question"
|
||||||
:cmsWidgetName="widget.existingOrNewQuestion"
|
:cmsWidgetName="widget.existingOrNewQuestion"
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="answers"
|
:answers="duplicateOrders"
|
||||||
groupName="existingOrNewQuestionOption"
|
groupName="existingOrNewQuestionOption"
|
||||||
buttonTypeString="listButton"
|
buttonTypeString="listButton"
|
||||||
isRequired
|
isRequired
|
||||||
:validationRules="rules.selectionRequired" />
|
:validationRules="rules.selectionRequired" />
|
||||||
|
<buttonMain
|
||||||
|
:variant="buttonVariants.primary"
|
||||||
|
buttonText="Start a new claim"
|
||||||
|
class="mt-5 w-100"
|
||||||
|
@clickEvent="startNewClaim" />
|
||||||
<siteFooter
|
<siteFooter
|
||||||
ref="siteFooter"
|
ref="siteFooter"
|
||||||
class="my-5"
|
class="my-5"
|
||||||
|
|
@ -48,6 +53,8 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||||
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
|
import buttonVariants from '@/constants/button-variants';
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
|
||||||
|
|
@ -66,7 +73,8 @@ export default {
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
siteFooter,
|
siteFooter,
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form
|
Form,
|
||||||
|
buttonMain
|
||||||
},
|
},
|
||||||
mixins: [BaseFormMixin],
|
mixins: [BaseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -91,23 +99,16 @@ export default {
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
selectionRequired: globalRules.OPTION_REQUIRED
|
selectionRequired: globalRules.OPTION_REQUIRED
|
||||||
}
|
},
|
||||||
|
buttonVariants
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
questionText() {
|
questionText() {
|
||||||
return this.getCmsContent(this.widget.existingOrNewQuestion, 'QuestionText');
|
return this.getCmsContent(this.widget.existingOrNewQuestion, 'QuestionText');
|
||||||
},
|
},
|
||||||
answersFromCms() {
|
|
||||||
return (
|
|
||||||
this.getCmsContent(this.widget.existingOrNewQuestion, 'Answers') ?? []
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getNewOrderSelectionName() {
|
|
||||||
return this.answersFromCms?.[0]?.Name ?? '';
|
|
||||||
},
|
|
||||||
duplicateOrders() {
|
duplicateOrders() {
|
||||||
const duplicateOrderText = 'Finish Existing Claim';
|
const duplicateOrderText = 'Finish existing claim';
|
||||||
const orders = useMainStore().applicationUser.duplicateOrders;
|
const orders = useMainStore().applicationUser.duplicateOrders;
|
||||||
return (
|
return (
|
||||||
orders?.map((o) => {
|
orders?.map((o) => {
|
||||||
|
|
@ -127,9 +128,6 @@ export default {
|
||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
);
|
);
|
||||||
},
|
|
||||||
answers() {
|
|
||||||
return [...this.duplicateOrders, ...this.answersFromCms];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -197,6 +195,10 @@ export default {
|
||||||
this.$route
|
this.$route
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async startNewClaim() {
|
||||||
|
this.selectedAnswer = 'NewClaim';
|
||||||
|
await this.forwardButtonAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -216,21 +218,36 @@ export default {
|
||||||
.subheader-secondary {
|
.subheader-secondary {
|
||||||
margin-top: map-get($spacers, 2);
|
margin-top: map-get($spacers, 2);
|
||||||
}
|
}
|
||||||
p {
|
|
||||||
span {
|
|
||||||
font-size: $h6-font-size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.duplicate-check-question {
|
.duplicate-check-question {
|
||||||
.question-text {
|
.question-text {
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
margin-top: map-get($spacers, 4);
|
margin-top: map-get($spacers, 4);
|
||||||
margin-bottom: map-get($spacers, 2);
|
margin-bottom: 0.625rem !important;
|
||||||
|
span {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.form-test-error {
|
.form-test-error {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
|
span {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.question-text.d-flex {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-button-content {
|
||||||
|
span.small {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.25rem !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
// Components
|
// Components
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import entryPage from '@/layouts/entry-page/entry-page.vue';
|
import entryPage from '@/layouts/entry-page/entry-page.vue';
|
||||||
|
|
||||||
import { shallowMount } from '@vue/test-utils';
|
// Supporting Files
|
||||||
|
import baseMixin from '@/mixins/base-mixin';
|
||||||
|
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
import settleAllPromises from '@/helpers/layout-helper.js';
|
import settleAllPromises from '@/helpers/layout-helper.js';
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
import * as clientAuthHelper from '@/helpers/clientauth-helper';
|
||||||
|
|
||||||
// Mock our module for promises.
|
// Mock our module for promises.
|
||||||
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
||||||
|
|
@ -15,34 +20,192 @@ jest.mock('@/helpers/cms-content-helper', () => ({
|
||||||
setupModalLinks: jest.fn()
|
setupModalLinks: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/** @ignore */
|
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}) {
|
||||||
function setupMocks(queryString) {
|
|
||||||
const mountOptions = getMountOptions({
|
const mountOptions = getMountOptions({
|
||||||
router: {
|
router: {
|
||||||
navigate: jest.fn()
|
navigate: jest.fn()
|
||||||
},
|
}
|
||||||
route: { queryString }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const wrapper = shallowMount(
|
const testingPinia = createTestingPinia({
|
||||||
entryPage,
|
initialState: {
|
||||||
mountOptions
|
main: mainInitialState
|
||||||
);
|
}
|
||||||
|
});
|
||||||
|
useMainStore(testingPinia);
|
||||||
|
methodToRunAfterInitializingStore();
|
||||||
|
|
||||||
const apiResponses = {};
|
mountOptions.global.plugins = [testingPinia];
|
||||||
|
mountOptions.data = () => (initialData);
|
||||||
|
|
||||||
|
const apiResponses = { cmsContent: {} };
|
||||||
|
|
||||||
settleAllPromises.mockImplementation(() => apiResponses);
|
settleAllPromises.mockImplementation(() => apiResponses);
|
||||||
fetchCmsContentForPage.mockImplementation(() => { });
|
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||||
|
|
||||||
|
const wrapper = shallowMount(entryPage, mountOptions);
|
||||||
|
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {});
|
||||||
|
wrapper.vm.setCmsContent = jest.fn();
|
||||||
|
wrapper.vm.$router.navigateWithSpinner = jest.fn();
|
||||||
|
wrapper.vm.navigateForward = baseMixin.methods.navigateForward;
|
||||||
|
|
||||||
return { wrapper };
|
return { wrapper };
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('entry-page.vue', () => {
|
describe('entry-page.vue', () => {
|
||||||
test('should render', () => {
|
test('shows unauthorized message when not authorized', async () => {
|
||||||
const queryString = 'policynumber="123456"';
|
const wrapper = shallowMount(entryPage, getMountOptions());
|
||||||
const { wrapper } = setupMocks(queryString);
|
wrapper.setData({ unauthorized: true });
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
expect(wrapper.find('#message').isVisible()).toBe(true);
|
||||||
|
expect(wrapper.text()).toContain('Unauthorized Access.');
|
||||||
|
});
|
||||||
|
describe('validateClientTagOnEntry', () => {
|
||||||
|
let wrapper;
|
||||||
|
beforeEach(() => {
|
||||||
|
const mainInitialState = { issConfig: {} };
|
||||||
|
wrapper = getMountedComponent(mainInitialState).wrapper;
|
||||||
|
});
|
||||||
|
|
||||||
window.console.log(wrapper.vm.$route.query);
|
it('returns unauthorized if no clienttag', async () => {
|
||||||
expect(wrapper).toBeTruthy();
|
const result = await wrapper.vm.validateClientTagOnEntry({});
|
||||||
|
expect(result.isAuthorized).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns unauthorized if validateISSClientTag returns falsy', async () => {
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(null);
|
||||||
|
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc' });
|
||||||
|
expect(result.isAuthorized).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns authorized and clientData if valid and not RSAToken', async () => {
|
||||||
|
const resp = {
|
||||||
|
active: true,
|
||||||
|
accountName: 'Client',
|
||||||
|
authentication: '',
|
||||||
|
parentAccountNumber: 'P',
|
||||||
|
styleSheet: '',
|
||||||
|
coverageEnabled: true,
|
||||||
|
siteType: ''
|
||||||
|
};
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||||
|
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc' });
|
||||||
|
expect(result.isAuthorized).toBe(true);
|
||||||
|
expect(result.clientData).toEqual(resp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles RSAToken with valid signature and EncParams', async () => {
|
||||||
|
const resp = {
|
||||||
|
active: true,
|
||||||
|
accountName: 'Client',
|
||||||
|
authentication: 'RSAToken EncParams',
|
||||||
|
parentAccountNumber: 'P',
|
||||||
|
styleSheet: '',
|
||||||
|
coverageEnabled: true,
|
||||||
|
siteType: ''
|
||||||
|
};
|
||||||
|
const decryptedData = 'foo=bar&from=yesterday';
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientSignature').mockResolvedValueOnce({ valid: true, decryptedData });
|
||||||
|
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc', token: 'tok', signature: 'sig' });
|
||||||
|
expect(result.isAuthorized).toBe(true);
|
||||||
|
expect(result.clientData).toEqual(resp);
|
||||||
|
expect(result.decryptedParams).toEqual({ foo: 'bar', from: 'yesterday' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles RSAToken with invalid signature', async () => {
|
||||||
|
const resp = {
|
||||||
|
active: true,
|
||||||
|
accountName: 'Client',
|
||||||
|
authentication: 'RSAToken',
|
||||||
|
parentAccountNumber: 'P',
|
||||||
|
styleSheet: '',
|
||||||
|
coverageEnabled: true,
|
||||||
|
siteType: ''
|
||||||
|
};
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||||
|
jest.spyOn(clientAuthHelper, 'validateISSClientSignature').mockResolvedValueOnce({ valid: false });
|
||||||
|
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc', token: 'tok', signature: 'sig' });
|
||||||
|
expect(result.isAuthorized).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('populateISSConfigValues sets issConfig fields and parses clientFlags', () => {
|
||||||
|
const mainInitialState = {
|
||||||
|
issConfig: {}
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent(mainInitialState);
|
||||||
|
const data = {
|
||||||
|
accountName: 'TestClient',
|
||||||
|
parentAccountNumber: '12345',
|
||||||
|
styleSheet: 'test-style',
|
||||||
|
coverageEnabled: true,
|
||||||
|
siteType: 'test-site',
|
||||||
|
clientFlags: JSON.stringify({
|
||||||
|
TPAEnabled: true,
|
||||||
|
ClientFullName: 'Full Name',
|
||||||
|
ClientDisplayName: 'Display Name',
|
||||||
|
ClaimRegistrationRequired: true,
|
||||||
|
EnableNoCompQuote: true
|
||||||
|
})
|
||||||
|
};
|
||||||
|
wrapper.vm.populateISSConfigValues(data);
|
||||||
|
const { issConfig } = wrapper.vm.mainStore;
|
||||||
|
expect(issConfig.clientName).toBe('TestClient');
|
||||||
|
expect(issConfig.clientFullName).toBe('Full Name');
|
||||||
|
expect(issConfig.clientDisplayName).toBe('Display Name');
|
||||||
|
expect(issConfig.parentAccountNumber).toBe('12345');
|
||||||
|
expect(issConfig.styleSheet).toBe('test-style');
|
||||||
|
expect(issConfig.isCoverageEnabled).toBe(true);
|
||||||
|
expect(issConfig.siteType).toBe('test-site');
|
||||||
|
expect(issConfig.enableTPAFlow).toBe(true);
|
||||||
|
expect(issConfig.isClaimRegistrationRequired).toBe(true);
|
||||||
|
expect(issConfig.enableNoCompQuote).toBe(true);
|
||||||
|
});
|
||||||
|
describe('combineClientParameters', () => {
|
||||||
|
let wrapper;
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = getMountedComponent({ issConfig: {} }).wrapper;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct params from config and query', () => {
|
||||||
|
const configParams = JSON.stringify(['PolicyNbr', 'DateOfLoss', 'Unused']);
|
||||||
|
const queryStringParams = { policynbr: '123', dateofloss: '2022-01-01', somethingelse: 'no' };
|
||||||
|
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||||
|
expect(result).toEqual({ policynbr: '123', dateofloss: '2022-01-01' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty object if configParams is not valid JSON', () => {
|
||||||
|
const configParams = 'notjson';
|
||||||
|
const queryStringParams = { policynbr: '123' };
|
||||||
|
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores params not present in query', () => {
|
||||||
|
const configParams = JSON.stringify(['PolicyNbr', 'MissingParam']);
|
||||||
|
const queryStringParams = { policynbr: '123' };
|
||||||
|
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||||
|
expect(result).toEqual({ policynbr: '123' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('populates store items from params', async () => {
|
||||||
|
const mainInitialState = {
|
||||||
|
issConfig: { disabledFields: {} },
|
||||||
|
order: { policy: {} }
|
||||||
|
};
|
||||||
|
const params = {
|
||||||
|
policynumber: 'ABC123',
|
||||||
|
policyzipcode: '90210',
|
||||||
|
dateofloss: '2022-01-01',
|
||||||
|
returnurl: 'http://success',
|
||||||
|
returnurl2: 'http://fail'
|
||||||
|
};
|
||||||
|
const { wrapper } = getMountedComponent(mainInitialState);
|
||||||
|
wrapper.vm.populateStoreItemsFromParams(params);
|
||||||
|
expect(wrapper.vm.mainStore.order.policy.policyNumber).toBe('ABC123');
|
||||||
|
expect(wrapper.vm.mainStore.order.policy.policyZipCode).toBe('90210');
|
||||||
|
expect(wrapper.vm.mainStore.order.policy.dateOfLoss).toBe('2022-01-01');
|
||||||
|
expect(wrapper.vm.mainStore.issConfig.successReturnURL).toBe('http://success');
|
||||||
|
expect(wrapper.vm.mainStore.issConfig.failureReturnURL).toBe('http://fail');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ export default {
|
||||||
},
|
},
|
||||||
populateISSConfigValues(data) {
|
populateISSConfigValues(data) {
|
||||||
this.mainStore.issConfig.clientName = data.accountName;
|
this.mainStore.issConfig.clientName = data.accountName;
|
||||||
|
this.mainStore.issConfig.clientFullName = data.accountName; // Defaults to use the client name.
|
||||||
this.mainStore.issConfig.clientDisplayName = data.accountName; // Defaults to use the client name.
|
this.mainStore.issConfig.clientDisplayName = data.accountName; // Defaults to use the client name.
|
||||||
this.mainStore.issConfig.parentAccountNumber = data.parentAccountNumber;
|
this.mainStore.issConfig.parentAccountNumber = data.parentAccountNumber;
|
||||||
this.mainStore.issConfig.styleSheet = data.styleSheet;
|
this.mainStore.issConfig.styleSheet = data.styleSheet;
|
||||||
|
|
@ -157,6 +158,10 @@ export default {
|
||||||
this.mainStore.issConfig.enableTPAFlow = true;
|
this.mainStore.issConfig.enableTPAFlow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientFlags.ClientFullName != null) {
|
||||||
|
this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName;
|
||||||
|
}
|
||||||
|
|
||||||
if (clientFlags.ClientDisplayName != null) {
|
if (clientFlags.ClientDisplayName != null) {
|
||||||
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName;
|
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
ref="policyVehiclesQuestion"
|
ref="policyVehiclesQuestion"
|
||||||
v-model="selectedVehicleVin"
|
v-model="selectedVehicleVin"
|
||||||
groupName="policyVehiclesQuestionOption"
|
groupName="policyVehiclesQuestionOption"
|
||||||
buttonTypeString="listButton"
|
buttonTypeString="listButton"
|
||||||
isOverflowScrollable
|
isOverflowScrollable
|
||||||
:answers="answers"
|
:answers="answers"
|
||||||
isRequired
|
isRequired
|
||||||
:validationRules="validationRules" />
|
:validationRules="validationRules" />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
cmsWidgetName="PolicyVehiclesQuestion"
|
cmsWidgetName="PolicyVehiclesQuestion"
|
||||||
:vehicles="VehiclesForQuestions"
|
:vehicles="VehiclesForQuestions"
|
||||||
:validationRules="rules.optionRequired"
|
:validationRules="rules.optionRequired"
|
||||||
class="mb-2"/>
|
class="mb-2" />
|
||||||
<buttonMain
|
<buttonMain
|
||||||
:variant="buttonVariants.primary"
|
:variant="buttonVariants.primary"
|
||||||
buttonText="Add another vehicle"
|
buttonText="Add another vehicle"
|
||||||
|
|
@ -114,7 +114,7 @@ export default {
|
||||||
const vehicles = this.policyVehicles;
|
const vehicles = this.policyVehicles;
|
||||||
const mappedData =
|
const mappedData =
|
||||||
vehicles?.map((v) => {
|
vehicles?.map((v) => {
|
||||||
const maskSymbol = 'X';
|
const maskSymbol = '*';
|
||||||
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
||||||
const vinEnd = v.vin.substring(v.vin.length - 6);
|
const vinEnd = v.vin.substring(v.vin.length - 6);
|
||||||
return {
|
return {
|
||||||
|
|
@ -122,7 +122,7 @@ export default {
|
||||||
vehicle: v,
|
vehicle: v,
|
||||||
Text: `${v.vehicleYear} ${v.vehicleMake} ${v.vehicleModel}`,
|
Text: `${v.vehicleYear} ${v.vehicleMake} ${v.vehicleModel}`,
|
||||||
Name: v.vin,
|
Name: v.vin,
|
||||||
SubText: `VIN ${vinStart}${vinEnd}`
|
SubText: `VIN: ${vinStart}${vinEnd}`
|
||||||
};
|
};
|
||||||
}) ?? [];
|
}) ?? [];
|
||||||
return mappedData;
|
return mappedData;
|
||||||
|
|
@ -304,6 +304,10 @@ export default {
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
padding-left: .9375rem;
|
padding-left: .9375rem;
|
||||||
padding-right: .9375rem;
|
padding-right: .9375rem;
|
||||||
|
|
||||||
|
.subheader-secondary p {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,4 +323,10 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-button-content {
|
||||||
|
span.small {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -43,41 +43,37 @@ function setupMocks(mockApiResponses) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('provider-preference.vue', () => {
|
describe('provider-preference.vue', () => {
|
||||||
test('Should navigate to safelite flow when safelite selected', () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
wrapper.vm.selectedProvider = 'SafeliteOption';
|
|
||||||
wrapper.vm.forwardButtonAction();
|
|
||||||
|
|
||||||
// Test
|
|
||||||
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE, 'provider-preference');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should navigate to tpa flow when TPAOption selected and TPA Flow Enabled', () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
wrapper.vm.selectedProvider = 'TPAOption';
|
|
||||||
useMainStore().issConfig.enableTPAFlow = true;
|
|
||||||
wrapper.vm.forwardButtonAction();
|
|
||||||
|
|
||||||
// Test
|
|
||||||
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED, 'provider-preference');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should navigate to tpa disabled route page when TPAOption selected and TPA Flow disabled', () => {
|
test('Should navigate to tpa disabled route page when TPAOption selected and TPA Flow disabled', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
wrapper.vm.selectedProvider = 'TPAOption';
|
|
||||||
useMainStore().issConfig.enableTPAFlow = false;
|
useMainStore().issConfig.enableTPAFlow = false;
|
||||||
wrapper.vm.forwardButtonAction();
|
wrapper.vm.findAnotherShopClicked();
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference');
|
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED, 'provider-preference');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should navigate to safelite flow when navigateWithTPARecalAnswer is called with SafeliteOption', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.navigateWithTPARecalAnswer('SafeliteOption');
|
||||||
|
|
||||||
|
// Test
|
||||||
|
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE, 'provider-preference');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should navigate to TPA flow when navigateWithTPARecalAnswer is called with TPAOption', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.navigateWithTPARecalAnswer('TPAOption');
|
||||||
|
|
||||||
|
// Test
|
||||||
|
expect(wrapper.vm.$router.navigate).toBeCalledWith(navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED, 'provider-preference');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,76 +15,70 @@
|
||||||
id="sub-header"
|
id="sub-header"
|
||||||
cmsWidgetName="SiteSubHeader"
|
cmsWidgetName="SiteSubHeader"
|
||||||
class="mb-0 mt-4 text-center" />
|
class="mb-0 mt-4 text-center" />
|
||||||
<buttonQuestion
|
<div
|
||||||
ref="buttonQuestion"
|
class="mt-4 mb-5"
|
||||||
v-model="selectedProvider"
|
v-html="scheduleWithSafeliteText"></div>
|
||||||
class="provider-preference"
|
<buttonMain
|
||||||
questionText="Select an option:"
|
ref="buttonMain"
|
||||||
:answers="prefAnswers"
|
class="full-width-button"
|
||||||
groupName="prefQuestions"
|
variant="navigation"
|
||||||
buttonTypeString="providerPrefRadio"
|
buttonText="Schedule now"
|
||||||
:validationRules="rules.optionRequired"
|
@clickEvent="scheduleWithSafelite" />
|
||||||
isRequired />
|
<div
|
||||||
|
class="mt-5 mb-5"
|
||||||
|
v-html="scheduleWithOtherText"></div>
|
||||||
|
<textLink
|
||||||
|
class="underlined-text"
|
||||||
|
linkType="navigation"
|
||||||
|
text="Find another shop"
|
||||||
|
href="#"
|
||||||
|
@clickEvent="findAnotherShopClicked" />
|
||||||
<siteFooter
|
<siteFooter
|
||||||
:ref="SITE_FOOTER_REF_NAME"
|
:ref="SITE_FOOTER_REF_NAME"
|
||||||
class="mt-5"
|
class="mt-6"
|
||||||
cmsWidgetName="SiteFooterWidget"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="isForwardActionDisabled"
|
:isForwardButtonHidden="true"
|
||||||
:isForwardButtonNavigationDisabled="isForwardNavigationDisabled"
|
@backClicked="navigateBack" />
|
||||||
@backClicked="navigateBack"
|
|
||||||
@forwardClicked="forwardButtonAction" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<contentGroupModal
|
|
||||||
:ref="RECAL_MODAL_REF_NAME"
|
|
||||||
cssModalHeadlineClass="text-center"
|
|
||||||
cmsWidgetName="RecalModal" />
|
|
||||||
<steeringModal
|
<steeringModal
|
||||||
:ref="STEERING_MODAL_REF_NAME"
|
:ref="STEERING_MODAL_REF_NAME"
|
||||||
cmsWidgetName="StateSteeringModal" />
|
cmsWidgetName="StateSteeringModal" />
|
||||||
<shopPreferenceModal
|
|
||||||
:ref="SHOP_PREFERENCE_MODAL_REF_NAME"
|
|
||||||
cmsWidgetName="ShopPreferenceDrawer"
|
|
||||||
:showSteeringLink="showSteeringLink"
|
|
||||||
@openSteering="openStateSteeringModal" />
|
|
||||||
<tpaRecalModal
|
<tpaRecalModal
|
||||||
:ref="TPA_RECAL_MODAL_REF_NAME"
|
:ref="TPA_RECAL_MODAL_REF_NAME"
|
||||||
cmsWidgetName="TPARecalModal"
|
cmsWidgetName="TPARecalModal"
|
||||||
|
buttonCmsWidgetName="TPARecalQuestion"
|
||||||
:ackError="ackError"
|
:ackError="ackError"
|
||||||
@buttonClick="navigateWithTPAAck" />
|
:noSelectionError="noSelectionError"
|
||||||
|
@buttonClick="navigateWithTPARecalAnswer"/>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
// Import Supporting Files
|
// Import Supporting Files
|
||||||
import {
|
import {
|
||||||
fetchCmsContentForPage,
|
fetchCmsContentForPage,
|
||||||
setupModalLink,
|
|
||||||
setupModalLinks
|
setupModalLinks
|
||||||
} from '@/helpers/cms-content-helper';
|
} from '@/helpers/cms-content-helper';
|
||||||
import settleAllPromises from '@/helpers/layout-helper';
|
import settleAllPromises from '@/helpers/layout-helper';
|
||||||
import errorMessages from '@/constants/error-messages';
|
import errorMessages from '@/constants/error-messages';
|
||||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||||
import issPageValues from '@/router/router-constants/issPage-values';
|
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
||||||
|
import bailoutMessage from '@/constants/bailoutMessage';
|
||||||
|
import baseFormMixin from '@/mixins/base-form-mixin';
|
||||||
|
import PROVIDER_PREFERENCE_OPTIONS from '@/constants/provider-preference';
|
||||||
|
|
||||||
// Import Component
|
// Import Component
|
||||||
import baseFormMixin from '@/mixins/base-form-mixin';
|
|
||||||
import { Form } from 'vee-validate';
|
import { Form } from 'vee-validate';
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import steeringModal from '@/layouts/provider-preference/steering-modal/steering-modal.vue';
|
import steeringModal from '@/layouts/provider-preference/steering-modal/steering-modal.vue';
|
||||||
import shopPreferenceModal from '@/layouts/provider-preference/shop-preference-modal/shop-preference-modal.vue';
|
|
||||||
import tpaRecalModal from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal.vue';
|
import tpaRecalModal from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-modal.vue';
|
||||||
import globalRules from '@/constants/global-rules';
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
import bailoutMessage from '@/constants/bailoutMessage';
|
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||||
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
|
|
||||||
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
|
||||||
|
|
||||||
const options = { SAFELITE: 'SafeliteOption', TPA: 'TPAOption' };
|
|
||||||
const RECAL_MODAL_REF_NAME = 'RecalModal';
|
|
||||||
const STEERING_MODAL_REF_NAME = 'StateSteeringModal';
|
const STEERING_MODAL_REF_NAME = 'StateSteeringModal';
|
||||||
const SHOP_PREFERENCE_MODAL_REF_NAME = 'ShopPreferenceDrawer';
|
|
||||||
const TPA_RECAL_MODAL_REF_NAME = 'TPARecalModal';
|
const TPA_RECAL_MODAL_REF_NAME = 'TPARecalModal';
|
||||||
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
const SITE_FOOTER_REF_NAME = 'siteFooter';
|
||||||
|
|
||||||
|
|
@ -97,10 +91,10 @@ export default {
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form,
|
Form,
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
|
buttonMain,
|
||||||
steeringModal,
|
steeringModal,
|
||||||
shopPreferenceModal,
|
|
||||||
tpaRecalModal,
|
tpaRecalModal,
|
||||||
contentGroupModal
|
textLink
|
||||||
},
|
},
|
||||||
mixins: [baseFormMixin],
|
mixins: [baseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -115,85 +109,37 @@ export default {
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
// open steering modal if it has body text, the state is defined in the CMS content and doesn't
|
||||||
vm.showSteeringLink = !!vm.$refs[STEERING_MODAL_REF_NAME].ModalBodyText;
|
// populate if the state is not listed in the CMS content
|
||||||
if (vm.showSteeringLink) {
|
if (!!vm.$refs[STEERING_MODAL_REF_NAME].ModalBodyText) {
|
||||||
vm.openStateSteeringModal();
|
vm.openStateSteeringModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedProvider: null,
|
providerPreferenceOptions: PROVIDER_PREFERENCE_OPTIONS,
|
||||||
showSteeringLink: false,
|
|
||||||
tpaAcknowledgement: false,
|
|
||||||
rules: {
|
|
||||||
optionRequired: globalRules.OPTION_REQUIRED
|
|
||||||
},
|
|
||||||
RECAL_MODAL_REF_NAME,
|
|
||||||
STEERING_MODAL_REF_NAME,
|
STEERING_MODAL_REF_NAME,
|
||||||
SHOP_PREFERENCE_MODAL_REF_NAME,
|
|
||||||
TPA_RECAL_MODAL_REF_NAME,
|
TPA_RECAL_MODAL_REF_NAME,
|
||||||
SITE_FOOTER_REF_NAME
|
SITE_FOOTER_REF_NAME
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isForwardActionDisabled() {
|
|
||||||
return this.selectedProvider === null;
|
|
||||||
},
|
|
||||||
isForwardNavigationDisabled() {
|
|
||||||
return this.selectedProvider === options.TPA && !this.tpaAcknowledgement;
|
|
||||||
},
|
|
||||||
prefAnswers() {
|
|
||||||
const cmsAnswersContent = [
|
|
||||||
{
|
|
||||||
cmsWidgetName: options.SAFELITE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cmsWidgetName: options.TPA
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// if cms content has not yet loaded, skip
|
|
||||||
if (
|
|
||||||
!this.getCmsContent(
|
|
||||||
cmsAnswersContent[0].cmsWidgetName,
|
|
||||||
'HeaderText'
|
|
||||||
)
|
|
||||||
|| this.getCmsContent(
|
|
||||||
cmsAnswersContent[0].cmsWidgetName,
|
|
||||||
'HeaderText'
|
|
||||||
) === ''
|
|
||||||
) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const modifiedAnswers = cmsAnswersContent.map((answer) => ({
|
|
||||||
value: answer.cmsWidgetName,
|
|
||||||
buttonLabel: this.getHeaderTextFromCms(answer.cmsWidgetName),
|
|
||||||
buttonLabelSubCopy: this.getSubheaderTextFromCms(answer.cmsWidgetName),
|
|
||||||
buttonBodyCopy: this.getBodyTextFromCms(answer.cmsWidgetName)
|
|
||||||
}));
|
|
||||||
return modifiedAnswers;
|
|
||||||
},
|
|
||||||
ackError() {
|
ackError() {
|
||||||
return errorMessages.ACKNOWLEDGEMENT_REQUIRED;
|
return errorMessages.ACKNOWLEDGEMENT_REQUIRED;
|
||||||
}
|
},
|
||||||
},
|
noSelectionError() {
|
||||||
watch: {
|
return errorMessages.NO_SELECTION_REQUIRED;
|
||||||
prefAnswers(newValue, oldValue) {
|
},
|
||||||
if (newValue !== oldValue) {
|
scheduleWithSafeliteText() {
|
||||||
setupModalLink(this, RECAL_MODAL_REF_NAME);
|
return this.getCmsContent('ScheduleWithSafeliteText', 'BodyText');
|
||||||
}
|
},
|
||||||
|
scheduleWithOtherText() {
|
||||||
|
return this.getCmsContent('ScheduleWithOtherText', 'BodyText');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
setupModalLinks(this);
|
setupModalLinks(this);
|
||||||
const pageData = this.mainStore.pageData(issPageValues.PROVIDER_PREFERENCE);
|
|
||||||
this.selectedProvider = pageData?.selectedProvider
|
|
||||||
? pageData.selectedProvider
|
|
||||||
: null;
|
|
||||||
this.tpaAcknowledgement = pageData?.tpaAcknowledgement
|
|
||||||
? pageData.tpaAcknowledgement
|
|
||||||
: false;
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getHeaderTextFromCms(cmsWidgetName) {
|
getHeaderTextFromCms(cmsWidgetName) {
|
||||||
|
|
@ -213,50 +159,38 @@ export default {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
navigateForward(scenario) {
|
navigateForward(scenario) {
|
||||||
|
showIssLoadingModal(true);
|
||||||
this.$router.navigate(scenario, this.$route);
|
this.$router.navigate(scenario, this.$route);
|
||||||
},
|
},
|
||||||
navigateWithTPAAck() {
|
navigateWithTPARecalAnswer(answer) {
|
||||||
this.mainStore.saveProviderPreferenceData({
|
if (answer === this.providerPreferenceOptions.TPA) {
|
||||||
selectedProvider: this.selectedProvider,
|
this.scheduleWithTPA();
|
||||||
tpaAcknowledgement: this.tpaAcknowledgement
|
} else {
|
||||||
});
|
this.scheduleWithSafelite();
|
||||||
showIssLoadingModal(true);
|
}
|
||||||
this.navigateForward(this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED);
|
|
||||||
},
|
},
|
||||||
forwardButtonAction() {
|
scheduleWithSafelite() {
|
||||||
if (this.selectedProvider) {
|
this.mainStore.updateIsSafeliteProvider(true);
|
||||||
let scenario = null;
|
const scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_SAFELITE;
|
||||||
switch (this.selectedProvider) {
|
this.navigateForward(scenario);
|
||||||
case options.SAFELITE:
|
},
|
||||||
this.mainStore.updateIsSafeliteProvider(true);
|
scheduleWithTPA() {
|
||||||
scenario =
|
this.mainStore.updateIsSafeliteProvider(false);
|
||||||
this.navigationScenarios
|
const scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_ENABLED;
|
||||||
.CLICKED_FORWARD_WITH_SAFELITE;
|
this.navigateForward(scenario);
|
||||||
break;
|
},
|
||||||
case options.TPA:
|
findAnotherShopClicked() {
|
||||||
this.mainStore.updateIsSafeliteProvider(false);
|
if (this.mainStore.issConfig.enableTPAFlow) {
|
||||||
if (this.mainStore.issConfig.enableTPAFlow) {
|
if (this.mainStore.hasRecalibrationPart) {
|
||||||
if (this.mainStore.hasRecalibrationPart) {
|
this.$refs[TPA_RECAL_MODAL_REF_NAME].openModal();
|
||||||
this.$refs[TPA_RECAL_MODAL_REF_NAME].openModal();
|
return;
|
||||||
return;
|
} else {
|
||||||
}
|
this.scheduleWithTPA();
|
||||||
scenario =
|
|
||||||
this.navigationScenarios
|
|
||||||
.CLICKED_FORWARD_WITH_TPA_ENABLED;
|
|
||||||
} else {
|
|
||||||
this.mainStore.setBailout(bailoutMessage.TPANotEnabled());
|
|
||||||
scenario =
|
|
||||||
this.navigationScenarios
|
|
||||||
.CLICKED_FORWARD_WITH_TPA_DISABLED;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.mainStore.setBailout(bailoutMessage.TPANotEnabled());
|
||||||
|
const scenario = this.navigationScenarios.CLICKED_FORWARD_WITH_TPA_DISABLED;
|
||||||
this.navigateForward(scenario);
|
this.navigateForward(scenario);
|
||||||
this.mainStore.saveProviderPreferenceData({
|
|
||||||
selectedProvider: this.selectedProvider,
|
|
||||||
tpaAcknowledgement: this.tpaAcknowledgement
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openStateSteeringModal() {
|
openStateSteeringModal() {
|
||||||
|
|
@ -279,12 +213,11 @@ export default {
|
||||||
#sub-header span {
|
#sub-header span {
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
.question-text {
|
.full-width-button {
|
||||||
margin-top: 0;
|
width: 100%;
|
||||||
margin-bottom: 0.5rem;
|
}
|
||||||
& > span {
|
.underlined-text {
|
||||||
text-align: left;
|
text-decoration: underline;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
:deep(.safeliteLogo) {
|
:deep(.safeliteLogo) {
|
||||||
background-image: url(~@/assets/img/icons/logo.svg);
|
background-image: url(~@/assets/img/icons/logo.svg);
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
|
||||||
import settleAllPromises from '@/helpers/layout-helper.js';
|
|
||||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
|
||||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
|
||||||
import shopPreferenceModal from '@/layouts/provider-preference/shop-preference-modal/shop-preference-modal.vue';
|
|
||||||
|
|
||||||
// Mock our module for promises.
|
|
||||||
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
|
||||||
|
|
||||||
// Mock fetchCmsContentForPage
|
|
||||||
jest.mock('@/helpers/cms-content-helper', () => ({
|
|
||||||
fetchCmsContentForPage: jest.fn(),
|
|
||||||
setupModalLinks: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
/** @ignore */
|
|
||||||
function setupMocks(propsData) {
|
|
||||||
const mountOptions = getMountOptions({
|
|
||||||
router: {
|
|
||||||
navigate: jest.fn()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mountOptions.propsData = propsData;
|
|
||||||
|
|
||||||
const wrapper = shallowMount(
|
|
||||||
shopPreferenceModal,
|
|
||||||
mountOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const apiResponses = {};
|
|
||||||
|
|
||||||
settleAllPromises.mockImplementation(() => apiResponses);
|
|
||||||
fetchCmsContentForPage.mockImplementation(() => { });
|
|
||||||
|
|
||||||
return { wrapper };
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('provider-preference.vue', () => {
|
|
||||||
test('Should display state specific steering modal link when in state with steering language', async () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({ showSteeringLink: true });
|
|
||||||
|
|
||||||
// Act
|
|
||||||
|
|
||||||
// Test
|
|
||||||
expect(wrapper.vm.showSteeringLink).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should not display state specific steering modal link when not in state with steering language', async () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = setupMocks({ showSteeringLink: false });
|
|
||||||
|
|
||||||
// Act
|
|
||||||
|
|
||||||
// Test
|
|
||||||
expect(wrapper.vm.showSteeringLink).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
<template>
|
|
||||||
<modal
|
|
||||||
:ref="ModalName"
|
|
||||||
:modalId="ModalName"
|
|
||||||
:onModalClosedCallback="onModalClosed"
|
|
||||||
:footerButtonText="ModalCloseButtonText"
|
|
||||||
@footerButtonEvent="footerButtonClick">
|
|
||||||
<h5 class="text-center mb-4">
|
|
||||||
{{ ModalHeadline }}
|
|
||||||
</h5>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="mb-4"
|
|
||||||
v-html="ModalBodyText"></div>
|
|
||||||
<a
|
|
||||||
v-if="showSteeringLink"
|
|
||||||
class="modal-text"
|
|
||||||
@click="openSteeringModal"
|
|
||||||
v-html="ModalSubBodyText">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import modal from '@/digital-components/modal/modal.vue';
|
|
||||||
import states from '@/constants/states';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'content-group-modal',
|
|
||||||
components: {
|
|
||||||
modal
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
cmsWidgetName: String,
|
|
||||||
showSteeringLink: Boolean
|
|
||||||
},
|
|
||||||
emits: ['openSteering'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
clickedSteeringLink: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
ModalName() {
|
|
||||||
return this.cmsWidgetName;
|
|
||||||
},
|
|
||||||
ModalHeadline() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
|
|
||||||
},
|
|
||||||
ModalSubheadertext() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'SubheaderText');
|
|
||||||
},
|
|
||||||
ModalBodyText() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
|
|
||||||
},
|
|
||||||
ModalSubBodyText() {
|
|
||||||
const header = this.getCmsContent(this.cmsWidgetName, 'BodyText2');
|
|
||||||
return header.replace('{custom:state}', states[this.mainStore.order.customer.address.state]);
|
|
||||||
},
|
|
||||||
ModalImage() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'Image');
|
|
||||||
},
|
|
||||||
ModalCloseButtonText() {
|
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
openModal() {
|
|
||||||
this.$refs[this.ModalName].openModal();
|
|
||||||
},
|
|
||||||
onModalClosed() {
|
|
||||||
if (this.clickedSteeringLink) {
|
|
||||||
this.$emit('openSteering');
|
|
||||||
this.clickedSteeringLink = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
footerButtonClick() {
|
|
||||||
this.$refs[this.ModalName].closeModal();
|
|
||||||
},
|
|
||||||
openSteeringModal() {
|
|
||||||
this.clickedSteeringLink = true;
|
|
||||||
this.$refs[this.ModalName].closeModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.modal {
|
|
||||||
&.modal-component {
|
|
||||||
.modal-dialog {
|
|
||||||
.modal-content {
|
|
||||||
.modal-body {
|
|
||||||
.modal-sub-body {
|
|
||||||
color: $gray-600;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -38,16 +38,26 @@ describe('tpa-Recal-Modal.vue', () => {
|
||||||
test('should show error if not acknowledged', () => {
|
test('should show error if not acknowledged', () => {
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
wrapper.vm.acknowledged = false;
|
wrapper.vm.acknowledged = false;
|
||||||
|
wrapper.vm.tpaRecalAnswer = 'TPAOption';
|
||||||
wrapper.vm.footerButtonClick();
|
wrapper.vm.footerButtonClick();
|
||||||
|
|
||||||
expect(wrapper.vm.showError).toBeTruthy();
|
expect(wrapper.vm.showAcknowledgementError).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not show error if acknowledged', () => {
|
test('should not show error if acknowledged', () => {
|
||||||
const { wrapper } = setupMocks();
|
const { wrapper } = setupMocks();
|
||||||
wrapper.vm.acknowledged = true;
|
wrapper.vm.acknowledged = true;
|
||||||
|
wrapper.vm.tpaRecalAnswer = 'TPAOption';
|
||||||
wrapper.vm.footerButtonClick();
|
wrapper.vm.footerButtonClick();
|
||||||
|
|
||||||
expect(wrapper.vm.showError).toBeFalsy();
|
expect(wrapper.vm.showAcknowledgementError).toBeFalsy();
|
||||||
|
});
|
||||||
|
test('should show error if no answer selected', () => {
|
||||||
|
const { wrapper } = setupMocks();
|
||||||
|
wrapper.vm.tpaRecalAnswer = '';
|
||||||
|
wrapper.vm.tpaRecalAnswer = undefined;
|
||||||
|
wrapper.vm.footerButtonClick();
|
||||||
|
|
||||||
|
expect(wrapper.vm.showNoSelectionError).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,23 @@
|
||||||
<tpaRecalToggle
|
<tpaRecalToggle
|
||||||
class="mb-5"
|
class="mb-5"
|
||||||
cmsWidgetName="TPARecalModalToggle" />
|
cmsWidgetName="TPARecalModalToggle" />
|
||||||
|
<buttonQuestion
|
||||||
|
ref="tpaRecalQuestion"
|
||||||
|
v-model="tpaRecalAnswer"
|
||||||
|
class="radioQuestion safelite-or-tpa-question windshield-chip-count-question mb-4"
|
||||||
|
:cmsWidgetName="buttonCmsWidgetName"
|
||||||
|
:questionText="tpaRecalQuestionText"
|
||||||
|
:answers="tpaRecalAnswersFromCms"
|
||||||
|
groupName="tpaRecalQuestion"
|
||||||
|
buttonID="tpaRecalQuestion"
|
||||||
|
buttonTypeString="listButtonHorizontal"
|
||||||
|
isRequired></buttonQuestion>
|
||||||
<checkBox
|
<checkBox
|
||||||
|
v-if="showAcknowledgementCheckbox"
|
||||||
ref="tpaAcknowledgement"
|
ref="tpaAcknowledgement"
|
||||||
v-model="acknowledged"
|
v-model="acknowledged"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
:class="showError && ' has-error'"
|
:class="showAcknowledgementError && ' has-error'"
|
||||||
:validationRules="rules.optionRequired"
|
:validationRules="rules.optionRequired"
|
||||||
checkboxName="tpaAcknowledgement"
|
checkboxName="tpaAcknowledgement"
|
||||||
buttonID="tpaAcknowledgement"
|
buttonID="tpaAcknowledgement"
|
||||||
|
|
@ -29,9 +41,9 @@
|
||||||
:screenReaderOnlyText="ModalSubBodyText"
|
:screenReaderOnlyText="ModalSubBodyText"
|
||||||
isRequired />
|
isRequired />
|
||||||
<div
|
<div
|
||||||
v-if="showError"
|
v-if="showAcknowledgementError || showNoSelectionError"
|
||||||
class="row form-test-error mt-1">
|
class="row form-test-error mt-1">
|
||||||
<p>{{ ackError }}</p>
|
<p>{{ errorMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -42,24 +54,31 @@
|
||||||
import modal from '@/digital-components/modal/modal.vue';
|
import modal from '@/digital-components/modal/modal.vue';
|
||||||
import tpaRecalToggle from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-toggle/tpa-recal-toggle.vue';
|
import tpaRecalToggle from '@/layouts/provider-preference/tpa-recal-modal/tpa-recal-toggle/tpa-recal-toggle.vue';
|
||||||
import checkBox from '@/ux-components/checkbox/checkbox.vue';
|
import checkBox from '@/ux-components/checkbox/checkbox.vue';
|
||||||
|
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||||
import globalRules from '@/constants/global-rules';
|
import globalRules from '@/constants/global-rules';
|
||||||
|
import PROVIDER_PREFERENCE_OPTIONS from '@/constants/provider-preference';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'content-group-modal',
|
name: 'content-group-modal',
|
||||||
components: {
|
components: {
|
||||||
modal,
|
modal,
|
||||||
tpaRecalToggle,
|
tpaRecalToggle,
|
||||||
checkBox
|
checkBox,
|
||||||
|
buttonQuestion
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
ackError: String
|
buttonCmsWidgetName: String,
|
||||||
|
ackError: String,
|
||||||
|
noSelectionError: String
|
||||||
},
|
},
|
||||||
emits: ['buttonClick'],
|
emits: ['buttonClick'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
acknowledged: false,
|
acknowledged: false,
|
||||||
showError: false,
|
showAcknowledgementError: false,
|
||||||
|
showNoSelectionError: false,
|
||||||
|
tpaRecalAnswer: '',
|
||||||
rules: {
|
rules: {
|
||||||
optionRequired: globalRules.OPTION_REQUIRED
|
optionRequired: globalRules.OPTION_REQUIRED
|
||||||
}
|
}
|
||||||
|
|
@ -81,13 +100,34 @@ export default {
|
||||||
ModalCloseButtonText() {
|
ModalCloseButtonText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
|
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
|
||||||
},
|
},
|
||||||
|
tpaRecalQuestionText() {
|
||||||
|
return this.getCmsContent(this.buttonCmsWidgetName, 'QuestionText');
|
||||||
|
},
|
||||||
|
tpaRecalAnswersFromCms() {
|
||||||
|
return this.getCmsContent(this.buttonCmsWidgetName, 'Answers');
|
||||||
|
},
|
||||||
|
showAcknowledgementCheckbox() {
|
||||||
|
return this.tpaRecalAnswer === PROVIDER_PREFERENCE_OPTIONS.TPA;
|
||||||
|
},
|
||||||
isButtonDisabled() {
|
isButtonDisabled() {
|
||||||
return !this.acknowledged;
|
return !this.acknowledged;
|
||||||
|
},
|
||||||
|
errorMessage() {
|
||||||
|
if (this.showNoSelectionError) {
|
||||||
|
return this.noSelectionError;
|
||||||
|
} else if (this.showAcknowledgementError) {
|
||||||
|
return this.ackError;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
acknowledged() {
|
acknowledged() {
|
||||||
this.showError = false;
|
this.showAcknowledgementError = false;
|
||||||
|
},
|
||||||
|
tpaRecalAnswer() {
|
||||||
|
this.showNoSelectionError = false;
|
||||||
|
this.showAcknowledgementError = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -96,12 +136,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
footerButtonClick() {
|
footerButtonClick() {
|
||||||
// check if acked, if not show error
|
if (this.tpaRecalAnswer && (!this.showAcknowledgementCheckbox || this.acknowledged )) {
|
||||||
if (this.acknowledged) {
|
|
||||||
this.$refs[this.ModalName]?.closeModal();
|
this.$refs[this.ModalName]?.closeModal();
|
||||||
this.$emit('buttonClick');
|
this.$emit('buttonClick', this.tpaRecalAnswer);
|
||||||
|
} else if (!this.tpaRecalAnswer) {
|
||||||
|
this.showNoSelectionError = true;
|
||||||
|
this.showAcknowledgementError = false;
|
||||||
} else {
|
} else {
|
||||||
this.showError = true;
|
this.showAcknowledgementError = true;
|
||||||
|
this.showNoSelectionError = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,29 +173,16 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.safelite-or-tpa-question {
|
||||||
|
:deep(.question-text) {
|
||||||
.form-check {
|
span {
|
||||||
.form-check-input {
|
font-weight: 500;
|
||||||
&:checked {
|
|
||||||
+ label {
|
|
||||||
p {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
font-size: 1rem !important;
|
|
||||||
color: $gray-600 !important;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p {
|
|
||||||
font-weight: 400 !important;
|
|
||||||
font-size: 1rem !important;
|
|
||||||
color: $gray-600 !important;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-test-error {
|
.form-test-error {
|
||||||
p {
|
p {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { createTestingPinia } from '@pinia/testing';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||||
import { useMainStore } from '@/store/index.js';
|
import { useMainStore } from '@/store/index.js';
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
|
||||||
|
|
||||||
// Mock fetchCmsContentForPage
|
// Mock fetchCmsContentForPage
|
||||||
jest.mock('@/helpers/cms-content-helper', () => ({
|
jest.mock('@/helpers/cms-content-helper', () => ({
|
||||||
|
|
@ -214,7 +213,7 @@ describe('schedule-page.vue', () => {
|
||||||
// Act
|
// Act
|
||||||
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
||||||
'2023-01-01',
|
'2023-01-01',
|
||||||
'2023-01-31'
|
'2023-01-15'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -236,7 +235,7 @@ describe('schedule-page.vue', () => {
|
||||||
estimatedServiceMinutesMaximum: 120
|
estimatedServiceMinutesMaximum: 120
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
test('Should call API service in days of 34 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
test('Should call API service in days of 15 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = getShallowMountedComponent();
|
const { wrapper } = getShallowMountedComponent();
|
||||||
wrapper.vm.selectableDatesData = {
|
wrapper.vm.selectableDatesData = {
|
||||||
|
|
@ -264,7 +263,7 @@ describe('schedule-page.vue', () => {
|
||||||
// 2023-01-01 --> 2023-02-05
|
// 2023-01-01 --> 2023-02-05
|
||||||
// 2023-02-06 --> 2023-03-12
|
// 2023-02-06 --> 2023-03-12
|
||||||
// 2023-03-13 --> 2023-03-31
|
// 2023-03-13 --> 2023-03-31
|
||||||
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(3);
|
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(6);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
|
|
@ -290,32 +289,16 @@ describe('schedule-page.vue', () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(testValue).toStrictEqual('01234');
|
expect(testValue).toStrictEqual('01234');
|
||||||
});
|
});
|
||||||
test('getDisplayTextForMilitaryTime should return the correctly formatted string', () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = getShallowMountedComponent();
|
|
||||||
wrapper.vm.selectableDatesData = {
|
|
||||||
days: []
|
|
||||||
};
|
|
||||||
const timeInput1 = '15:00';
|
|
||||||
const timeInput2 = '15:30';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const testOutput1 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1);
|
|
||||||
const testOutput2 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2);
|
|
||||||
const testOutput3 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1, true);
|
|
||||||
const testOutput4 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2, true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(testOutput1).toBe('3:00 PM');
|
|
||||||
expect(testOutput2).toBe('3:30 PM');
|
|
||||||
expect(testOutput3).toBe('3 PM');
|
|
||||||
expect(testOutput4).toBe('3:30 PM');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test('forwardButtonAction should call route method navigateWithoutSaving', async () => {
|
test('forwardButtonAction should call route method navigateWithoutSaving', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = getShallowMountedComponent();
|
const { wrapper } = getShallowMountedComponent();
|
||||||
wrapper.vm.$router.navigate = jest.fn(() => ({}));
|
wrapper.vm.$router.navigate = jest.fn(() => ({}));
|
||||||
|
wrapper.vm.selectedTimeSlotInfo = {
|
||||||
|
timeSlot: {
|
||||||
|
routeCode: 'test-id'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<Form
|
||||||
ref="theForm"
|
ref="theForm"
|
||||||
v-slot="{ meta }"
|
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
@invalidSubmit="onInvalidSubmit">
|
@invalidSubmit="onInvalidSubmit">
|
||||||
<div class="fade-on-route-transition">
|
<div class="fade-on-route-transition">
|
||||||
|
|
@ -16,56 +15,27 @@
|
||||||
cmsWidgetName="ScheduleSubHeaderWidget"
|
cmsWidgetName="ScheduleSubHeaderWidget"
|
||||||
secondaryTextClasses="text-center small sub-text"
|
secondaryTextClasses="text-center small sub-text"
|
||||||
class="mt-4" />
|
class="mt-4" />
|
||||||
<template v-if="ChangeShopLink.length">
|
|
||||||
<textBlock
|
|
||||||
cmsWidgetName="ChangeShopLink"
|
|
||||||
justifyText="center"
|
|
||||||
class="mb-5 text-link-small change-shop-link"
|
|
||||||
:marginTopSizeOverride="1" />
|
|
||||||
</template>
|
|
||||||
<div class="main-content-container">
|
<div class="main-content-container">
|
||||||
<locationAlerts
|
<locationAlerts
|
||||||
ref="locationAlerts"
|
ref="locationAlerts"
|
||||||
cmsWidgetPrefix="LocationAlert-" />
|
cmsWidgetPrefix="LocationAlert-" />
|
||||||
<datePicker
|
<datePicker
|
||||||
ref="datePicker"
|
ref="datePicker"
|
||||||
v-model="selectedDate"
|
v-model="selectedTimeSlotInfo"
|
||||||
customComponentId="dateQuestion"
|
customComponentId="dateQuestion"
|
||||||
selectableDatesSetting="custom"
|
selectableDatesSetting="custom"
|
||||||
class="text-link-small"
|
class="text-link-small"
|
||||||
|
:showTimeSlotError="showDatePickerError"
|
||||||
:customSelectableDatesCallback="
|
:customSelectableDatesCallback="
|
||||||
getAvailableDatesMethod
|
getAvailableDatesMethod
|
||||||
"
|
"
|
||||||
validationRules="date-required"
|
@dateSelected="dateSelectedFromPicker"
|
||||||
@dateClicked="openInshopTimeSlotsModal" />
|
@timeSlotSelected="timeSlotSelectedFromPicker" />
|
||||||
<timeSlotModalQuestion
|
|
||||||
ref="timeSlotModalQuestion"
|
|
||||||
v-model="selectedTimeSlotInfo"
|
|
||||||
customComponentId="timeSlotModalQuestion"
|
|
||||||
cmsWidgetName="TimeSlotModalQuestion"
|
|
||||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
|
||||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
|
||||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
|
||||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
|
||||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
|
||||||
:selectedDate="selectedDate"
|
|
||||||
:appointmentType="appointmentType"
|
|
||||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
|
||||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
|
||||||
:estimatedServiceMinutesMinimum="
|
|
||||||
selectableDatesData.estimatedServiceMinutesMinimum
|
|
||||||
"
|
|
||||||
:estimatedServiceMinutesMaximum="
|
|
||||||
selectableDatesData.estimatedServiceMinutesMaximum
|
|
||||||
"
|
|
||||||
validationRules="time-slot-selection-required"
|
|
||||||
@timeSlotModalClosed="timeSlotModalClosed"
|
|
||||||
@timeSlotSelected="forwardButtonAction" />
|
|
||||||
<siteFooter
|
<siteFooter
|
||||||
ref="navbar"
|
ref="navbar"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
cmsWidgetName="SiteFooterWidget"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardButtonNavigationDisabled="!isFormValid"
|
||||||
@backClicked="navigateBack"
|
@backClicked="navigateBack"
|
||||||
@forwardClicked="forwardButtonAction" />
|
@forwardClicked="forwardButtonAction" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,9 +50,7 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||||
import locationAlerts from '@/layouts/schedule-page/location-alerts/location-alerts.vue';
|
import locationAlerts from '@/layouts/schedule-page/location-alerts/location-alerts.vue';
|
||||||
import datePicker from '@/digital-components/date-picker/date-picker.vue';
|
import datePicker from '@/digital-components/date-picker/date-picker.vue';
|
||||||
import timeSlotModalQuestion from '@/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue';
|
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import {
|
import {
|
||||||
|
|
@ -97,27 +65,15 @@ import {
|
||||||
} from '@/helpers/cms-content-helper';
|
} from '@/helpers/cms-content-helper';
|
||||||
import {
|
import {
|
||||||
calcDaysBetweenDates,
|
calcDaysBetweenDates,
|
||||||
convertDateStringToDate,
|
|
||||||
sumDateString
|
sumDateString
|
||||||
} from '@/helpers/date-helper';
|
} from '@/helpers/date-helper';
|
||||||
import settleAllPromises from '@/helpers/layout-helper';
|
import settleAllPromises from '@/helpers/layout-helper';
|
||||||
import { Form, defineRule } from 'vee-validate';
|
import { Form } from 'vee-validate';
|
||||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||||
import errorMessages from '@/constants/error-messages';
|
|
||||||
import { required } from '@/helpers/validation-rules';
|
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
|
|
||||||
// DEFINE VALIDATION RULES
|
|
||||||
defineRule('date-required', required(errorMessages.DATE_REQUIRED));
|
|
||||||
defineRule('time-slot-selection-required', (value) => {
|
|
||||||
if (value?.timeSlot?.routeCode == null) {
|
|
||||||
return errorMessages.DATE_REQUIRED;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
|
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
|
||||||
|
|
||||||
const getAvailableDates = async (
|
const getAvailableDates = async (
|
||||||
startDateString,
|
startDateString,
|
||||||
|
|
@ -127,13 +83,11 @@ const getAvailableDates = async (
|
||||||
) => {
|
) => {
|
||||||
const apiEndDateLimit = sumDateString(
|
const apiEndDateLimit = sumDateString(
|
||||||
startDateString,
|
startDateString,
|
||||||
TIME_SLOTS_CALL_DAYS_LIMIT
|
TIME_SLOTS_CALL_DAYS_LIMIT - 1
|
||||||
);
|
);
|
||||||
const difference = calcDaysBetweenDates(startDateString, endDateString);
|
const difference = calcDaysBetweenDates(startDateString, endDateString);
|
||||||
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
|
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
|
||||||
const storeActionConfigs = [];
|
const storeActionConfigs = [];
|
||||||
const timeSlotsData = {};
|
|
||||||
timeSlotsData.days = [];
|
|
||||||
let apiStartDate = startDateString;
|
let apiStartDate = startDateString;
|
||||||
let apiEndDate = endDateString;
|
let apiEndDate = endDateString;
|
||||||
|
|
||||||
|
|
@ -144,7 +98,7 @@ const getAvailableDates = async (
|
||||||
apiStartDate = sumDateString(apiEndDate, 1);
|
apiStartDate = sumDateString(apiEndDate, 1);
|
||||||
apiEndDate = sumDateString(
|
apiEndDate = sumDateString(
|
||||||
apiStartDate,
|
apiStartDate,
|
||||||
TIME_SLOTS_CALL_DAYS_LIMIT
|
TIME_SLOTS_CALL_DAYS_LIMIT - 1
|
||||||
);
|
);
|
||||||
|
|
||||||
if (i === apiCallsCount) {
|
if (i === apiCallsCount) {
|
||||||
|
|
@ -154,11 +108,7 @@ const getAvailableDates = async (
|
||||||
apiEndDate = apiEndDateLimit;
|
apiEndDate = apiEndDateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
|
||||||
appointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
|| appointmentType
|
|
||||||
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
|
||||||
) {
|
|
||||||
storeActionConfig = {
|
storeActionConfig = {
|
||||||
storeAction: GET_MOBILE_TIME_SLOTS,
|
storeAction: GET_MOBILE_TIME_SLOTS,
|
||||||
payload: {
|
payload: {
|
||||||
|
|
@ -232,9 +182,7 @@ export default {
|
||||||
siteSubHeader,
|
siteSubHeader,
|
||||||
locationAlerts,
|
locationAlerts,
|
||||||
datePicker,
|
datePicker,
|
||||||
timeSlotModalQuestion,
|
|
||||||
siteFooter,
|
siteFooter,
|
||||||
textBlock,
|
|
||||||
// eslint-disable-next-line vue/no-reserved-component-names
|
// eslint-disable-next-line vue/no-reserved-component-names
|
||||||
Form
|
Form
|
||||||
},
|
},
|
||||||
|
|
@ -300,7 +248,6 @@ export default {
|
||||||
resultMap.datePickerInitialData.initialShopTimeSlotsResponse,
|
resultMap.datePickerInitialData.initialShopTimeSlotsResponse,
|
||||||
resultMap.premiumFeeWithPrice
|
resultMap.premiumFeeWithPrice
|
||||||
);
|
);
|
||||||
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
|
@ -312,6 +259,7 @@ export default {
|
||||||
selectedDate: this.getSelectedDate(),
|
selectedDate: this.getSelectedDate(),
|
||||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||||
selectableDatesData: [],
|
selectableDatesData: [],
|
||||||
|
showDatePickerError: false,
|
||||||
mobilePremiumAppointmentFee: null
|
mobilePremiumAppointmentFee: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -325,38 +273,14 @@ export default {
|
||||||
appointmentType() {
|
appointmentType() {
|
||||||
return useMainStore().order.serviceLocation.appointmentType;
|
return useMainStore().order.serviceLocation.appointmentType;
|
||||||
},
|
},
|
||||||
timeSlotsForSelectedDate() {
|
isFormValid() {
|
||||||
if (!this.selectedDate) {
|
const hasTimeSlotSelected = this.selectedTimeSlotInfo?.timeSlot?.routeCode != null;
|
||||||
return null;
|
return hasTimeSlotSelected;
|
||||||
}
|
|
||||||
|
|
||||||
return this.selectableDatesData.days?.find((selectableDate) => selectableDate.date === this.selectedDate);
|
|
||||||
},
|
},
|
||||||
supportingItems() {
|
supportingItems() {
|
||||||
return useMainStore().lineItems.supportingItems;
|
return useMainStore().lineItems.supportingItems;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
selectedDate(newValue, oldValue) {
|
|
||||||
// Clear time slot selection if date selected changes
|
|
||||||
if (newValue !== oldValue) {
|
|
||||||
this.selectedTimeSlotInfo = {
|
|
||||||
timeSlot: {
|
|
||||||
date: null,
|
|
||||||
routeCode: null,
|
|
||||||
startTime: null,
|
|
||||||
endTime: null,
|
|
||||||
jobMaxMinutes: null,
|
|
||||||
jobMinMinutes: null
|
|
||||||
},
|
|
||||||
isPremiumAppointment: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedTimeSlotInfo(newValue) {
|
|
||||||
this.updateFooterButtonText(newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
splitCopyOnCMSPlaceHolder,
|
splitCopyOnCMSPlaceHolder,
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
|
|
@ -400,9 +324,6 @@ export default {
|
||||||
getServiceZipCtuCodeFromStore() {
|
getServiceZipCtuCodeFromStore() {
|
||||||
return this.mainStore.order.serviceLocation.zipCodeCtu;
|
return this.mainStore.order.serviceLocation.zipCodeCtu;
|
||||||
},
|
},
|
||||||
openInshopTimeSlotsModal() {
|
|
||||||
this.$refs.timeSlotModalQuestion.openModal();
|
|
||||||
},
|
|
||||||
getSelectedDate() {
|
getSelectedDate() {
|
||||||
return this.mainStore.order.schedule.date;
|
return this.mainStore.order.schedule.date;
|
||||||
},
|
},
|
||||||
|
|
@ -420,64 +341,21 @@ export default {
|
||||||
|
|
||||||
return selectedTimeSlotInfo;
|
return selectedTimeSlotInfo;
|
||||||
},
|
},
|
||||||
timeSlotModalClosed() {
|
dateSelectedFromPicker(date) {
|
||||||
// Clear the selectedDate if no timeSlot has been selected
|
this.selectedDate = date;
|
||||||
if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
|
this.showDatePickerError = false;
|
||||||
this.selectedDate = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
updateFooterButtonText(timeSlotInfo) {
|
timeSlotSelectedFromPicker(timeSlot) {
|
||||||
let navbarButtonText;
|
this.selectedTimeSlotInfo = timeSlot;
|
||||||
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
|
this.showDatePickerError = false;
|
||||||
navbarButtonText = 'Continue';
|
|
||||||
} else {
|
|
||||||
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(timeSlotInfo.timeSlot.date)}`;
|
|
||||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
|
||||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(timeSlotInfo.timeSlot.startTime)}`;
|
|
||||||
} else if (
|
|
||||||
this.appointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
&& !timeSlotInfo.isPremiumAppointment
|
|
||||||
) {
|
|
||||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
|
||||||
timeSlotInfo.timeSlot.startTime,
|
|
||||||
true
|
|
||||||
)} - ${this.getDisplayTextForMilitaryTime(
|
|
||||||
timeSlotInfo.timeSlot.endTime,
|
|
||||||
true
|
|
||||||
)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$refs.navbar.updateButtonText(navbarButtonText);
|
|
||||||
},
|
|
||||||
convertSelectedDateToShortMonthAndDay(selectedDate) {
|
|
||||||
// This conversion ensures we don't get get GMT induced date changes
|
|
||||||
const dateObject = convertDateStringToDate(selectedDate);
|
|
||||||
return dateObject.toLocaleDateString('en-us', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getDisplayTextForMilitaryTime(
|
|
||||||
militaryTimeInput,
|
|
||||||
shouldTrimMinutesIfEmpty = false
|
|
||||||
) {
|
|
||||||
// Expected input: "HH:MM"
|
|
||||||
let hours = parseInt(militaryTimeInput.split(':')[0], 10);
|
|
||||||
const minutes = militaryTimeInput.split(':')[1];
|
|
||||||
const meridianNotation = hours > 11 ? 'PM' : 'AM';
|
|
||||||
|
|
||||||
if (hours > 12) {
|
|
||||||
hours -= 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldTrimMinutesIfEmpty && minutes === '00') {
|
|
||||||
return `${hours} ${meridianNotation}`;
|
|
||||||
}
|
|
||||||
return `${hours}:${minutes} ${meridianNotation}`;
|
|
||||||
},
|
},
|
||||||
forwardButtonAction() {
|
forwardButtonAction() {
|
||||||
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
|
if (!this.isFormValid) {
|
||||||
|
this.showDatePickerError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.CLICKED_FORWARD,
|
this.navigationScenarios.CLICKED_FORWARD,
|
||||||
this.$route
|
this.$route
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,6 @@ describe('welcome-page.vue', () => {
|
||||||
const state = wrapper.findComponent({ ref: 'state' });
|
const state = wrapper.findComponent({ ref: 'state' });
|
||||||
const glassOnlyDamage = wrapper.findComponent({ ref: 'glassOnlyDamage' });
|
const glassOnlyDamage = wrapper.findComponent({ ref: 'glassOnlyDamage' });
|
||||||
const phoneNumber = wrapper.findComponent({ ref: 'phoneNumber' });
|
const phoneNumber = wrapper.findComponent({ ref: 'phoneNumber' });
|
||||||
const email = wrapper.findComponent({ ref: 'email' });
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(policyNumber.exists()).toBe(true);
|
expect(policyNumber.exists()).toBe(true);
|
||||||
|
|
@ -146,7 +145,6 @@ describe('welcome-page.vue', () => {
|
||||||
expect(state.exists()).toBe(false);
|
expect(state.exists()).toBe(false);
|
||||||
expect(glassOnlyDamage.exists()).toBe(false);
|
expect(glassOnlyDamage.exists()).toBe(false);
|
||||||
expect(phoneNumber.exists()).toBe(true);
|
expect(phoneNumber.exists()).toBe(true);
|
||||||
expect(email.exists()).toBe(true);
|
|
||||||
});
|
});
|
||||||
test('Policy zip field should be visible at all times', async () => {
|
test('Policy zip field should be visible at all times', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<div class="welcome-page-container iss-heritage-content-container-width">
|
<div class="welcome-page-container iss-heritage-content-container-width">
|
||||||
<siteSubHeader
|
<siteSubHeader
|
||||||
cmsWidgetName="SiteSubHeaderWidget"
|
cmsWidgetName="SiteSubHeaderWidget"
|
||||||
class="mt-4" />
|
class="form-group" />
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
ref="policyNumber"
|
ref="policyNumber"
|
||||||
v-model="welcomePageModel.policyNumber"
|
v-model="welcomePageModel.policyNumber"
|
||||||
|
|
@ -22,16 +22,6 @@
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
:isDisabled="isPolicyHolderDisabled"
|
:isDisabled="isPolicyHolderDisabled"
|
||||||
:validationRules="rules.policyNumber" />
|
:validationRules="rules.policyNumber" />
|
||||||
<textboxQuestion
|
|
||||||
ref="policyZip"
|
|
||||||
v-model="welcomePageModel.policyZipCode"
|
|
||||||
inputId="policyZipCode"
|
|
||||||
cmsWidgetName="PolicyZipQuestion"
|
|
||||||
isRequired
|
|
||||||
mask="#####"
|
|
||||||
:isDisabled="isPolicyZipDisabled"
|
|
||||||
:validationRules="rules.policyZip"
|
|
||||||
class="mt-3" />
|
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
ref="phoneNumber"
|
ref="phoneNumber"
|
||||||
v-model="welcomePageModel.phoneNumber"
|
v-model="welcomePageModel.phoneNumber"
|
||||||
|
|
@ -41,7 +31,8 @@
|
||||||
isRequired
|
isRequired
|
||||||
:mask="phoneMask"
|
:mask="phoneMask"
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
class="mt-3" />
|
placeholderText="###-###-####"
|
||||||
|
class="form-group" />
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
ref="extension"
|
ref="extension"
|
||||||
v-model="welcomePageModel.extension"
|
v-model="welcomePageModel.extension"
|
||||||
|
|
@ -49,7 +40,7 @@
|
||||||
cmsWidgetName="ExtensionQuestion"
|
cmsWidgetName="ExtensionQuestion"
|
||||||
:validationRules="rules.extension"
|
:validationRules="rules.extension"
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
class="mt-3" />
|
class="form-group" />
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
ref="dateOfLoss"
|
ref="dateOfLoss"
|
||||||
v-model="welcomePageModel.dateOfLoss"
|
v-model="welcomePageModel.dateOfLoss"
|
||||||
|
|
@ -62,7 +53,7 @@
|
||||||
:max="new Date().toJSON().slice(0, 10)"
|
:max="new Date().toJSON().slice(0, 10)"
|
||||||
:min="'1972-12-01'"
|
:min="'1972-12-01'"
|
||||||
:validationRules="rules.lossDate"
|
:validationRules="rules.lossDate"
|
||||||
class="mt-3" />
|
class="form-group" />
|
||||||
<textBlock
|
<textBlock
|
||||||
cmsWidgetName="DamageDateEstimateWidget"
|
cmsWidgetName="DamageDateEstimateWidget"
|
||||||
typeStyle="small"
|
typeStyle="small"
|
||||||
|
|
@ -77,44 +68,45 @@
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
:validationRules="rules.damageOption"
|
:validationRules="rules.damageOption"
|
||||||
placeHolderText="Select an option"
|
placeHolderText="Select an option"
|
||||||
class="mt-3" />
|
class="form-group" />
|
||||||
|
<textboxQuestion
|
||||||
|
ref="policyZip"
|
||||||
|
v-model="welcomePageModel.policyZipCode"
|
||||||
|
inputId="policyZipCode"
|
||||||
|
cmsWidgetName="PolicyZipQuestion"
|
||||||
|
isRequired
|
||||||
|
mask="#####"
|
||||||
|
:isDisabled="isPolicyZipDisabled"
|
||||||
|
:validationRules="rules.policyZip"
|
||||||
|
class="form-group" />
|
||||||
<dropdownQuestion
|
<dropdownQuestion
|
||||||
v-if="displayDamageStateQuestion"
|
v-if="displayDamageStateQuestion"
|
||||||
id="welcomeDropdown"
|
id="welcomeDropdown"
|
||||||
ref="state"
|
ref="state"
|
||||||
v-model="welcomePageModel.damageState"
|
v-model="welcomePageModel.damageState"
|
||||||
class="mt-3"
|
class="form-group"
|
||||||
cmsWidgetName="DamageStateQuestion"
|
cmsWidgetName="DamageStateQuestion"
|
||||||
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
|
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
|
||||||
:options="getStates"
|
:options="getStates"
|
||||||
:validationRules="rules.lossState"
|
:validationRules="rules.lossState"
|
||||||
isRequired
|
isRequired
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
placeHolderText="Select an option" />
|
placeHolderText="Select State" />
|
||||||
<textboxQuestion
|
<textboxQuestion
|
||||||
v-if="displayDamageCityQuestion"
|
v-if="displayDamageCityQuestion"
|
||||||
ref="damageCity"
|
ref="damageCity"
|
||||||
v-model="welcomePageModel.damageCity"
|
v-model="welcomePageModel.damageCity"
|
||||||
class="mt-3"
|
class="form-group"
|
||||||
inputId="damageCityField"
|
inputId="damageCityField"
|
||||||
cmsWidgetName="DamageCityQuestion"
|
cmsWidgetName="DamageCityQuestion"
|
||||||
isRequired
|
isRequired
|
||||||
disableAutoFill
|
disableAutoFill
|
||||||
:validationRules="rules.lossCity" />
|
:validationRules="rules.lossCity" />
|
||||||
<textboxQuestion
|
|
||||||
ref="email"
|
|
||||||
v-model="welcomePageModel.email"
|
|
||||||
inputId="emailField"
|
|
||||||
cmsWidgetName="EmailAddressQuestion"
|
|
||||||
:validationRules="rules.email"
|
|
||||||
isRequired
|
|
||||||
disableAutoFill
|
|
||||||
class="mt-3" />
|
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
v-if="displayGlassOnlyQuestion"
|
v-if="displayGlassOnlyQuestion"
|
||||||
ref="glassOnlyDamage"
|
ref="glassOnlyDamage"
|
||||||
v-model="welcomePageModel.isDamageGlassOnly"
|
v-model="welcomePageModel.isDamageGlassOnly"
|
||||||
class="px-0 mt-3"
|
class="px-0 form-group"
|
||||||
cmsWidgetName="GlassOnlyQuestion"
|
cmsWidgetName="GlassOnlyQuestion"
|
||||||
inputId="isDamageGlassOnly"
|
inputId="isDamageGlassOnly"
|
||||||
:answers="DamageGlassOnlyOptions"
|
:answers="DamageGlassOnlyOptions"
|
||||||
|
|
@ -142,17 +134,9 @@
|
||||||
:isDismissible="false" />
|
:isDismissible="false" />
|
||||||
<siteFooter
|
<siteFooter
|
||||||
ref="siteFooter"
|
ref="siteFooter"
|
||||||
class="mt-3"
|
|
||||||
cmsWidgetName="SiteFooterWidget"
|
cmsWidgetName="SiteFooterWidget"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardActionDisabled="!meta.valid"
|
||||||
@ForwardClicked="forwardButtonAction" />
|
@ForwardClicked="forwardButtonAction" />
|
||||||
<textBlock
|
|
||||||
id="requestCallbackLink"
|
|
||||||
cmsWidgetName="HelpTextWidget"
|
|
||||||
linkType="navigation"
|
|
||||||
href="javascript:void(0)"
|
|
||||||
class="mb-5 text-left"
|
|
||||||
@clickEvent="handleHelpLinkClick" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,7 +241,6 @@ export default {
|
||||||
duplicates: [],
|
duplicates: [],
|
||||||
rules: {
|
rules: {
|
||||||
damageOption: 'damage-option-required',
|
damageOption: 'damage-option-required',
|
||||||
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
|
|
||||||
extension: `${globalRules.EXTENSION_FORMAT}`,
|
extension: `${globalRules.EXTENSION_FORMAT}`,
|
||||||
lossCity: `${globalRules.DATE_OF_LOSS_CITY_REQUIRED}|${globalRules.DATE_OF_LOSS_CITY_FORMAT}`,
|
lossCity: `${globalRules.DATE_OF_LOSS_CITY_REQUIRED}|${globalRules.DATE_OF_LOSS_CITY_FORMAT}`,
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
|
|
@ -302,7 +285,11 @@ export default {
|
||||||
return !!this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
|
return !!this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
|
||||||
},
|
},
|
||||||
getStates() {
|
getStates() {
|
||||||
return states;
|
return Object.keys(states).reduce((acc, key) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
acc[key] = states[key].toUpperCase();
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
},
|
},
|
||||||
isPolicyHolderDisabled() {
|
isPolicyHolderDisabled() {
|
||||||
return !!this.mainStore.issConfig.disabledFields.policyNumber;
|
return !!this.mainStore.issConfig.disabledFields.policyNumber;
|
||||||
|
|
@ -408,7 +395,6 @@ export default {
|
||||||
isDamageGlassOnly: this.mainStore.order.policy.isDamageGlassOnly,
|
isDamageGlassOnly: this.mainStore.order.policy.isDamageGlassOnly,
|
||||||
phoneNumber: this.mainStore.order.contactInfo.homePhone,
|
phoneNumber: this.mainStore.order.contactInfo.homePhone,
|
||||||
extension: this.mainStore.order.contactInfo.extension,
|
extension: this.mainStore.order.contactInfo.extension,
|
||||||
email: this.mainStore.order.customer.emailAddress,
|
|
||||||
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
|
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -425,7 +411,6 @@ export default {
|
||||||
this.welcomePageModel.isDamageGlassOnly = response.policy.isDamageGlassOnly;
|
this.welcomePageModel.isDamageGlassOnly = response.policy.isDamageGlassOnly;
|
||||||
this.welcomePageModel.phoneNumber = response.customer.homePhone;
|
this.welcomePageModel.phoneNumber = response.customer.homePhone;
|
||||||
this.welcomePageModel.extension = response.customer.extension;
|
this.welcomePageModel.extension = response.customer.extension;
|
||||||
this.welcomePageModel.email = response.customer.emailAddress;
|
|
||||||
},
|
},
|
||||||
findDamageCause(damageCause) {
|
findDamageCause(damageCause) {
|
||||||
const damageCauseOptions = this.DamageCauseOptions;
|
const damageCauseOptions = this.DamageCauseOptions;
|
||||||
|
|
@ -481,13 +466,6 @@ export default {
|
||||||
this.mainStore.order.loadedFromCookie = true;
|
this.mainStore.order.loadedFromCookie = true;
|
||||||
this.answeredContinueModal = true;
|
this.answeredContinueModal = true;
|
||||||
this.$refs.continueModal.closeModal();
|
this.$refs.continueModal.closeModal();
|
||||||
},
|
|
||||||
handleHelpLinkClick() {
|
|
||||||
useMainStore().setBailout(bailoutMessage.RequestCallback());
|
|
||||||
this.$router.navigate(
|
|
||||||
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
|
|
||||||
this.$route
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -508,6 +486,10 @@ form {
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
padding-left: .9375rem;
|
padding-left: .9375rem;
|
||||||
padding-right: .9375rem;
|
padding-right: .9375rem;
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import bailoutMessage from '@/constants/bailoutMessage';
|
||||||
import coverageStatuses from '@/constants/coverage-statuses';
|
import coverageStatuses from '@/constants/coverage-statuses';
|
||||||
import coverageType from '@/constants/coverage-type';
|
import coverageType from '@/constants/coverage-type';
|
||||||
import damageLocationsSelected from '@/constants/damage-locations-selected';
|
import damageLocationsSelected from '@/constants/damage-locations-selected';
|
||||||
import { endpoints } from '@/constants/endpoints';
|
import endpoints from '@/constants/endpoints';
|
||||||
import { experimentSettings, experimentTriggers, experimentUniverses } from '@/constants/experiments';
|
import { experimentSettings, experimentTriggers, experimentUniverses } from '@/constants/experiments';
|
||||||
import partNumberStrings from '@/constants/part-number-strings';
|
import partNumberStrings from '@/constants/part-number-strings';
|
||||||
import partTypeStrings from '@/constants/part-type-strings';
|
import partTypeStrings from '@/constants/part-type-strings';
|
||||||
|
|
@ -243,6 +243,7 @@ export const getDefaultState = () => ({
|
||||||
},
|
},
|
||||||
issConfig: {
|
issConfig: {
|
||||||
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
|
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
|
||||||
|
clientFullName: 'Generic Insurance', // this is the default and will be overriden by the client's name or client's full name.
|
||||||
clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name.
|
clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name.
|
||||||
clientHeader: {},
|
clientHeader: {},
|
||||||
styleSheet: '', // Stylesheet used by the client.
|
styleSheet: '', // Stylesheet used by the client.
|
||||||
|
|
@ -1351,10 +1352,11 @@ export const useMainStore = defineStore({
|
||||||
async setVehicle() {
|
async setVehicle() {
|
||||||
const encodedMake = encodeURIComponent(this.order.vehicle.make);
|
const encodedMake = encodeURIComponent(this.order.vehicle.make);
|
||||||
const encodedModel = encodeURIComponent(this.order.vehicle.model);
|
const encodedModel = encodeURIComponent(this.order.vehicle.model);
|
||||||
|
const encodedStyle = encodeURIComponent(this.order.vehicle.style);
|
||||||
const response = await globalMethods
|
const response = await globalMethods
|
||||||
.callHttpClient({
|
.callHttpClient({
|
||||||
method: endpoints.GetVehicle.method,
|
method: endpoints.GetVehicle.method,
|
||||||
endpoint: `${endpoints.GetVehicle.url}/${this.order.vehicle.year}/${encodedMake}/${encodedModel}/${this.order.vehicle.style}`,
|
endpoint: `${endpoints.GetVehicle.url}/${this.order.vehicle.year}/${encodedMake}/${encodedModel}/${encodedStyle}`,
|
||||||
payload: {}
|
payload: {}
|
||||||
});
|
});
|
||||||
this.updateVehicle(response.data);
|
this.updateVehicle(response.data);
|
||||||
|
|
@ -2154,6 +2156,7 @@ export const useMainStore = defineStore({
|
||||||
|
|
||||||
resetISSConfigState() {
|
resetISSConfigState() {
|
||||||
this.issConfig.clientName = 'Generic Insurance';
|
this.issConfig.clientName = 'Generic Insurance';
|
||||||
|
this.issConfig.clientFullName = 'Generic Insurance';
|
||||||
this.issConfig.clientDisplayName = 'Generic Insurance';
|
this.issConfig.clientDisplayName = 'Generic Insurance';
|
||||||
this.issConfig.clientHeader = {};
|
this.issConfig.clientHeader = {};
|
||||||
this.issConfig.parentAccountNumber = 0;
|
this.issConfig.parentAccountNumber = 0;
|
||||||
|
|
@ -2273,7 +2276,6 @@ export const useMainStore = defineStore({
|
||||||
this.order.policy.damageState = welcomePageModel?.damageState;
|
this.order.policy.damageState = welcomePageModel?.damageState;
|
||||||
this.order.policy.damageCity = welcomePageModel?.damageCity;
|
this.order.policy.damageCity = welcomePageModel?.damageCity;
|
||||||
this.order.policy.isDamageGlassOnly = welcomePageModel?.isDamageGlassOnly;
|
this.order.policy.isDamageGlassOnly = welcomePageModel?.isDamageGlassOnly;
|
||||||
this.order.customer.emailAddress = welcomePageModel?.email;
|
|
||||||
this.order.serviceLocation.zipCode = welcomePageModel?.policyZipCode;
|
this.order.serviceLocation.zipCode = welcomePageModel?.policyZipCode;
|
||||||
this.updatePhoneNumbers({
|
this.updatePhoneNumbers({
|
||||||
home: welcomePageModel?.phoneNumber,
|
home: welcomePageModel?.phoneNumber,
|
||||||
|
|
@ -2363,38 +2365,27 @@ export const useMainStore = defineStore({
|
||||||
const serviceLocationCity = serviceLocation.city;
|
const serviceLocationCity = serviceLocation.city;
|
||||||
const serviceLocationState = serviceLocation.state;
|
const serviceLocationState = serviceLocation.state;
|
||||||
const serviceLocationZipCode = serviceLocation.zipCode;
|
const serviceLocationZipCode = serviceLocation.zipCode;
|
||||||
|
|
||||||
const lineItemsQueryString = getTaxLineItemQueryString(pricedLineItems, 'lineItems');
|
|
||||||
|
|
||||||
let queryString = '';
|
|
||||||
if (appointmentType === AppointmentTypeStrings.MOBILE
|
|
||||||
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
|
|
||||||
queryString =
|
|
||||||
`ParentAccountNumber=${this.order.parentAccountNumber}`
|
|
||||||
+ `&BillToAccountNumber=${this.billToAccountNumber}`
|
|
||||||
+ `&ProviderNumber=${this.providerNumber}`
|
|
||||||
+ `&AppointmentType=${appointmentType}`
|
|
||||||
+ `&ServiceLocation.City=${serviceLocationCity}`
|
|
||||||
+ `&ServiceLocation.State=${serviceLocationState}`
|
|
||||||
+ `&ServiceLocation.ZipCode=${serviceLocationZipCode}`
|
|
||||||
+ `${lineItemsQueryString}`;
|
|
||||||
} else {
|
|
||||||
queryString =
|
|
||||||
`ParentAccountNumber=${this.order.parentAccountNumber}`
|
|
||||||
+ `&BillToAccountNumber=${this.billToAccountNumber}`
|
|
||||||
+ `&ProviderNumber=${this.providerNumber}`
|
|
||||||
+ `&AppointmentType=${appointmentType}`
|
|
||||||
+ `${lineItemsQueryString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineItemServerData = this.order.lineItems.serverData;
|
const lineItemServerData = this.order.lineItems.serverData;
|
||||||
if (lineItemServerData) {
|
|
||||||
queryString += `&ServerData=${encodeURIComponent(lineItemServerData)}`;
|
const isMobileApt = appointmentType === AppointmentTypeStrings.MOBILE
|
||||||
}
|
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
||||||
|
|
||||||
const retPricedLineItems = await globalMethods.callHttpClient({
|
const retPricedLineItems = await globalMethods.callHttpClient({
|
||||||
method: endpoints.TaxOrderItems.method,
|
method: endpoints.TaxOrderItems.method,
|
||||||
endpoint: `${endpoints.TaxOrderItems.url}?${queryString}`
|
endpoint: endpoints.TaxOrderItems.url,
|
||||||
|
payload: {
|
||||||
|
ParentAccountNumber: this.order.parentAccountNumber,
|
||||||
|
BillToAccountNumber: this.billToAccountNumber,
|
||||||
|
ProviderNumber: this.providerNumber,
|
||||||
|
AppointmentType: appointmentType,
|
||||||
|
PricedLineItems: getLineItemsFlattened(pricedLineItems),
|
||||||
|
ServiceLocation: {
|
||||||
|
City: isMobileApt ? serviceLocationCity : null,
|
||||||
|
State: isMobileApt ? serviceLocationState : null,
|
||||||
|
ZipCode: isMobileApt ? serviceLocationZipCode : null
|
||||||
|
},
|
||||||
|
ServerData: lineItemServerData || ''
|
||||||
|
}
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
this.order.lineItems.serverData = response.data.serverData;
|
this.order.lineItems.serverData = response.data.serverData;
|
||||||
return addTaxesToPricedLineItems(pricedLineItems, response.data.taxedLineItems);
|
return addTaxesToPricedLineItems(pricedLineItems, response.data.taxedLineItems);
|
||||||
|
|
@ -2402,9 +2393,6 @@ export const useMainStore = defineStore({
|
||||||
|
|
||||||
return retPricedLineItems;
|
return retPricedLineItems;
|
||||||
},
|
},
|
||||||
saveProviderPreferenceData(data) {
|
|
||||||
this.updatePageData({ page: issPageValues.PROVIDER_PREFERENCE, data });
|
|
||||||
},
|
|
||||||
addEventToBus(event) {
|
addEventToBus(event) {
|
||||||
this.applicationUser.eventBus.push(event);
|
this.applicationUser.eventBus.push(event);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from '@/helpers/data-generation.js';
|
} from '@/helpers/data-generation.js';
|
||||||
import coverageStatuses from '@/constants/coverage-statuses.js';
|
import coverageStatuses from '@/constants/coverage-statuses.js';
|
||||||
import { paymentMethods } from '@/constants/payment-method-constants';
|
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||||
import { endpoints } from '@/constants/endpoints';
|
import endpoints from '@/constants/endpoints';
|
||||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||||
import bailoutCode from '@/constants/bailoutCode';
|
import bailoutCode from '@/constants/bailoutCode';
|
||||||
import coverageType from '@/constants/coverage-type';
|
import coverageType from '@/constants/coverage-type';
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ body {
|
||||||
div.textbox-question {
|
div.textbox-question {
|
||||||
label {
|
label {
|
||||||
span.sub-caption {
|
span.sub-caption {
|
||||||
|
color: #525656;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,10 +204,10 @@ $heritage-btn-width: 9.0625rem; // 145px
|
||||||
@include heritage-btn-variant('secondary', $white, $heritage-blue-secondary, transparent, true);
|
@include heritage-btn-variant('secondary', $white, $heritage-blue-secondary, transparent, true);
|
||||||
@include heritage-btn-link('link', $blue, transparent, $heritage-blue-secondary);
|
@include heritage-btn-link('link', $blue, transparent, $heritage-blue-secondary);
|
||||||
|
|
||||||
@include heritage-btn-size('lg', 0.625rem, 1.25rem);
|
@include heritage-btn-size('lg', 0.625rem, 1.25rem);
|
||||||
@include heritage-btn-size('md', 7px, 15px);
|
@include heritage-btn-size('md', 7px, 15px);
|
||||||
@include heritage-btn-size('sm', 3px, 6px);
|
@include heritage-btn-size('sm', 3px, 6px);
|
||||||
@include heritage-btn-size('xs', 1px, 5px);
|
@include heritage-btn-size('xs', 1px, 5px);
|
||||||
|
|
||||||
&.btn-link {
|
&.btn-link {
|
||||||
--bs-btn-padding-x: 0px;
|
--bs-btn-padding-x: 0px;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ html {
|
||||||
&.list-button,
|
&.list-button,
|
||||||
&.list-button.list-group,
|
&.list-button.list-group,
|
||||||
&.list-card &.option-label {
|
&.list-card &.option-label {
|
||||||
|
border: 1px solid $red;
|
||||||
|
position: relative;
|
||||||
|
z-index: 4;
|
||||||
|
border-radius: 60px;
|
||||||
|
|
||||||
.button-content {
|
.button-content {
|
||||||
border: 1px solid $red;
|
border: 1px solid $red;
|
||||||
|
|
@ -25,6 +29,7 @@ html {
|
||||||
|
|
||||||
&.list-card {
|
&.list-card {
|
||||||
border: 1px solid $red;
|
border: 1px solid $red;
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ui-radio {
|
&.ui-radio {
|
||||||
|
|
@ -45,7 +50,23 @@ html {
|
||||||
|
|
||||||
// END HOVER
|
// END HOVER
|
||||||
|
|
||||||
&.list-button,
|
&.list-button {
|
||||||
|
color: $red;
|
||||||
|
|
||||||
|
input[type="checkbox"]:focus+label,
|
||||||
|
input[type="radio"]:focus+label {
|
||||||
|
box-shadow: 0 0 0 2.5px $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked+label {
|
||||||
|
box-shadow: 0 0 0 1px $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.list-card {
|
&.list-card {
|
||||||
color: $red;
|
color: $red;
|
||||||
|
|
||||||
|
|
@ -138,6 +159,7 @@ html {
|
||||||
border: 1px solid $success;
|
border: 1px solid $success;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
border-radius: 60px;
|
||||||
|
|
||||||
.button-content {
|
.button-content {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -155,6 +177,7 @@ html {
|
||||||
|
|
||||||
&.list-card {
|
&.list-card {
|
||||||
border: 1px solid $success;
|
border: 1px solid $success;
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ui-radio {
|
&.ui-radio {
|
||||||
|
|
@ -175,7 +198,23 @@ html {
|
||||||
|
|
||||||
// END HOVER
|
// END HOVER
|
||||||
|
|
||||||
&.list-button,
|
&.list-button {
|
||||||
|
color: $success;
|
||||||
|
|
||||||
|
input[type="checkbox"]:focus+label,
|
||||||
|
input[type="radio"]:focus+label {
|
||||||
|
box-shadow: 0 0 0 2.5px $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked+label {
|
||||||
|
box-shadow: 0 0 0 1px $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.list-card {
|
&.list-card {
|
||||||
color: $success;
|
color: $success;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ $red-100: #ffe6e4;
|
||||||
$red-200: #fcbfbb;
|
$red-200: #fcbfbb;
|
||||||
$red-300: #f89892;
|
$red-300: #f89892;
|
||||||
$red-400: #e65c53;
|
$red-400: #e65c53;
|
||||||
$red: #d4281c; // Default Red
|
$red: #db0020; // Default Red
|
||||||
$red-600: #ac160b;
|
$red-600: #ac160b;
|
||||||
$red-700: #840900;
|
$red-700: #840900;
|
||||||
$red-800: #5b0600;
|
$red-800: #5b0600;
|
||||||
|
|
@ -160,6 +160,7 @@ $border-radius-sm: 0.2rem;
|
||||||
$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course.
|
$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course.
|
||||||
$border-radius-xl: 1.375rem;
|
$border-radius-xl: 1.375rem;
|
||||||
$border-radius-pill: 50rem;
|
$border-radius-pill: 50rem;
|
||||||
|
$border-radius-list-button: 1.375rem; // Used for list buttons
|
||||||
|
|
||||||
//Progress Bar Styling
|
//Progress Bar Styling
|
||||||
$progress-bar-success-color: $green;
|
$progress-bar-success-color: $green;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<baseInputButton
|
<baseInputButton
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100">
|
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
|
||||||
<div
|
<div
|
||||||
:aria-label="buttonLabel"
|
:aria-label="buttonLabel"
|
||||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"
|
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"
|
||||||
|
|
@ -87,6 +87,7 @@ $heritage-checked-border-color: #0070d1;
|
||||||
&:checked + .list-button-content {
|
&:checked + .list-button-content {
|
||||||
background: $heritage-checked-background-color;
|
background: $heritage-checked-background-color;
|
||||||
border-color: $heritage-checked-border-color;
|
border-color: $heritage-checked-border-color;
|
||||||
|
box-shadow: 0 0 0 1px $blue;
|
||||||
&:not(.has-sub-copy) {
|
&:not(.has-sub-copy) {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $black;
|
color: $black;
|
||||||
|
|
@ -96,9 +97,6 @@ $heritage-checked-border-color: #0070d1;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
}
|
}
|
||||||
&:focus:checked + .list-button-content {
|
|
||||||
border-width: 2.5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.list-button-content {
|
.list-button-content {
|
||||||
|
|
@ -112,12 +110,9 @@ $heritage-checked-border-color: #0070d1;
|
||||||
box-shadow: $heritage-box-shadow;
|
box-shadow: $heritage-box-shadow;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin-bottom: .625rem;
|
|
||||||
height: 4.375rem;
|
|
||||||
|
|
||||||
&:not(.has-sub-copy) {
|
&:not(.has-sub-copy) {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
height: 2.8125rem;
|
|
||||||
color: $darker-gray;
|
color: $darker-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,4 +122,4 @@ $heritage-checked-border-color: #0070d1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
Reference in a new issue