diff --git a/libs/blocks/quiz-entry/quiz-entry.js b/libs/blocks/quiz-entry/quiz-entry.js index 8559d7f04e..5e569bfe16 100644 --- a/libs/blocks/quiz-entry/quiz-entry.js +++ b/libs/blocks/quiz-entry/quiz-entry.js @@ -4,6 +4,12 @@ import { mlField, getMLResults } from './mlField.js'; import { GetQuizOption } from './quizoption.js'; import { quizPopover, getSuggestions } from './quizPopover.js'; +export const locationWrapper = { + redirect: (url) => { + window.location = url; + }, +}; + const App = ({ quizPath, maxQuestions, @@ -222,7 +228,7 @@ const App = ({ if (questionCount.current === maxQuestions || currentQuizState.userFlow.length === 1) { if (!debug) { setSelectedQuestion(null); - window.location = quizPath; + locationWrapper.redirect(quizPath); } } else { setSelectedCards({}); diff --git a/libs/blocks/quiz-entry/quizoption.js b/libs/blocks/quiz-entry/quizoption.js index a2774c89b0..42db01b59b 100644 --- a/libs/blocks/quiz-entry/quizoption.js +++ b/libs/blocks/quiz-entry/quizoption.js @@ -89,13 +89,6 @@ export const GetQuizOption = ({ } }; - useEffect(() => { - const entry = document.querySelector('.quiz-entry'); - if (entry && entry.querySelector('.no-carousel')) { - entry.removeChild(entry.querySelector('.no-carousel')); - } - }, []); - return html`
${index > 0 && html``} diff --git a/libs/blocks/quiz-entry/utils.js b/libs/blocks/quiz-entry/utils.js index 0d04f20364..f5551f1fc4 100644 --- a/libs/blocks/quiz-entry/utils.js +++ b/libs/blocks/quiz-entry/utils.js @@ -78,7 +78,10 @@ export const handleSelections = (prevSelections, selectedQuestion, selections) = // de-dup any existing data if they use the ml field and cards. if (prevSelections.length > 0) { prevSelections.forEach((selection) => { - if (selection.selectedQuestion === selectedQuestion) { + const jsonSelectionSelectedQustion = JSON.stringify(selection.selectedQuestion); + const jsonSelectedQuesion = JSON.stringify(selectedQuestion[0].selectedQuestion); + const isSameQuestion = jsonSelectionSelectedQustion === jsonSelectedQuesion; + if (isSameQuestion) { selection.selectedCards = selections; isNewQuestion = false; } diff --git a/test/blocks/quiz-entry/mocks/mock-data.js b/test/blocks/quiz-entry/mocks/mock-data.js index 01d7a933d6..e317479c83 100644 --- a/test/blocks/quiz-entry/mocks/mock-data.js +++ b/test/blocks/quiz-entry/mocks/mock-data.js @@ -100,7 +100,7 @@ const resultsMock = { options: 'photo', title: 'Photography', text: 'Edit or organize my photos', - icon: '', + icon: 'https://milo.adobe.com/drafts/quiz/quiz-ai/search.svg', image: 'https://main--milo--adobecom.hlx.page/drafts/colloyd/quiz-entry/images/photography.png', }, { @@ -664,7 +664,7 @@ const resultsMock = { }, { options: 'video', - next: 'q-rather,q-video', + next: 'RESET', type: 'card', endpoint: '', 'api-key': '', diff --git a/test/blocks/quiz-entry/quiz-entry.test.js b/test/blocks/quiz-entry/quiz-entry.test.js index 993ee21594..d1de93beb2 100644 --- a/test/blocks/quiz-entry/quiz-entry.test.js +++ b/test/blocks/quiz-entry/quiz-entry.test.js @@ -3,7 +3,7 @@ import { readFile } from '@web/test-runner-commands'; import { expect } from '@esm-bundle/chai'; import sinon from 'sinon'; import init from '../../../libs/blocks/quiz-entry/quiz-entry.js'; -import { getSuggestions } from '../../../libs/blocks/quiz-entry/quizPopover.js'; // Correct the path as needed +import { getSuggestions } from '../../../libs/blocks/quiz-entry/quizPopover.js'; let fetchStub; let quizEntryElement; @@ -156,4 +156,147 @@ describe('Quiz Entry Component', () => { await new Promise((resolve) => setTimeout(resolve, 100)); expect(continueButton.classList.contains('disabled')).to.be.false; }); + it('should navigate the carousel using keyboard commands', async () => { + const options = document.querySelectorAll('.quiz-option'); + const option = document.querySelector('.quiz-option'); + option.click(); + await new Promise((resolve) => setTimeout(resolve, 100)); + const carousel = document.querySelector('.quiz-options-container'); + const rightArrowEvent = new KeyboardEvent('keydown', { key: 'ArrowRight' }); + const leftArrowEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft' }); + carousel.dispatchEvent(rightArrowEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + carousel.dispatchEvent(leftArrowEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + const leftArrow = document.querySelector('.carousel-arrow.arrow-prev'); + expect(leftArrow).to.not.exist; + + const tabKeyEvent = new KeyboardEvent('keydown', { key: 'Tab' }); + option.dispatchEvent(tabKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(option.classList.contains('selected')).to.be.true; + + const spaceKeyEvent = new KeyboardEvent('keydown', { key: ' ', keyCode: 32 }); + carousel.dispatchEvent(spaceKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const enterKeyEvent = new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + keyCode: 13, + }); + carousel.dispatchEvent(enterKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(options[1].classList.contains('selected')).to.be.false; + }); +}); + +describe('RTL Quiz Entry', () => { + beforeEach(async () => { + window.lana = { log: sinon.stub() }; + fetchStub = sinon.stub(window, 'fetch'); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ suggested_completions: ['designer desk', 'design logos'] }), + }); + document.body.innerHTML = await readFile({ path: './mocks/index.html' }); + document.documentElement.setAttribute('dir', 'rtl'); + quizEntryElement = document.querySelector('.quiz-entry'); + await init(quizEntryElement, quizConfig); + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should navigate the carousel using keyboard commands', async () => { + const options = document.querySelectorAll('.quiz-option'); + const option = document.querySelector('.quiz-option'); + option.click(); + await new Promise((resolve) => setTimeout(resolve, 100)); + const carousel = document.querySelector('.quiz-options-container'); + const rightArrowEvent = new KeyboardEvent('keydown', { key: 'ArrowRight' }); + const leftArrowEvent = new KeyboardEvent('keydown', { key: 'ArrowLeft' }); + carousel.dispatchEvent(rightArrowEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + carousel.dispatchEvent(leftArrowEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + const leftArrow = document.querySelector('.carousel-arrow.arrow-prev'); + expect(leftArrow).to.exist; + + const tabKeyEvent = new KeyboardEvent('keydown', { key: 'Tab' }); + option.dispatchEvent(tabKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(option.classList.contains('selected')).to.be.false; + + const spaceKeyEvent = new KeyboardEvent('keydown', { key: ' ', keyCode: 32 }); + carousel.dispatchEvent(spaceKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const enterKeyEvent = new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + keyCode: 13, + }); + carousel.dispatchEvent(enterKeyEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(options[1].classList.contains('selected')).to.be.false; + }); +}); + +describe('ML Result Trigger', () => { + beforeEach(async () => { + window.lana = { log: sinon.stub() }; + fetchStub = sinon.stub(window, 'fetch'); + const mockApiResponse = { + statusCode: 200, + data: { + data: [ + { + ficode: 'illustrator_cc', + prob: '0.33', + }, + { + ficode: 'indesign_cc', + prob: '0.27', + }, + { + ficode: 'free_spark', + prob: '0.22', + }, + ], + jobName: '', + }, + }; + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve(mockApiResponse.data), + }); + document.body.innerHTML = await readFile({ path: './mocks/index.html' }); + quizEntryElement = document.querySelector('.quiz-entry'); + await init(quizEntryElement, quizConfig); + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('Should trigger results fetching scenario', async () => { + const mlInputField = document.querySelector('#quiz-input'); + const testInput = 'design'; + const inputEvent = new Event('input', { bubbles: true }); + mlInputField.value = testInput; + mlInputField.dispatchEvent(inputEvent); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const enterKeyEvent = new KeyboardEvent('keypress', { + key: 'Enter', + code: 'Enter', + keyCode: 13, + }); + mlInputField.dispatchEvent(enterKeyEvent); + expect(mlInputField.value).to.equal('design'); + }); }); diff --git a/test/blocks/quiz-entry/utils.test.js b/test/blocks/quiz-entry/utils.test.js new file mode 100644 index 0000000000..700f349907 --- /dev/null +++ b/test/blocks/quiz-entry/utils.test.js @@ -0,0 +1,149 @@ +/* eslint-disable no-promise-executor-return */ +import { readFile } from '@web/test-runner-commands'; +import { expect } from '@esm-bundle/chai'; +import sinon from 'sinon'; +import { handleNext, getQuizJson, handleSelections, getQuizEntryData } from '../../../libs/blocks/quiz-entry/utils.js'; // Correct the path as needed + +let fetchStub; +const { default: mockData } = await import('./mocks/mock-data.js'); +const mockQuestionsData = mockData.questions; +const mockStringsData = mockData.strings; +const quizConfig = { + quizPath: '/drafts/quiz/', + maxQuestions: 1, + analyticsQuiz: 'uarv4', + analyticsType: 'cc:app-reco', + questionData: undefined, + stringsData: undefined, +}; +const selectedQuestion = { + questions: 'q-category', + 'max-selections': '3', + 'min-selections': '1', +}; +const userInputSelections = { photo: true }; +const userInputSelectionsNot = { '3d': true }; +const userInputSelectionsReset = { video: true }; + +const userFlow = []; +const nextFlow = { nextFlow: ['q-rather', 'q-photo'] }; +const nextFlowNot = { nextFlow: ['q-3d'] }; +const nextFlowReset = { nextFlow: [] }; +const prevSelections = []; +const selections = ['photo']; +const nextSelectionsExpected = { + nextSelections: [ + { + selectedCards: [ + 'photo', + ], + selectedQuestion: { + 'max-selections': '3', + 'min-selections': '1', + questions: 'q-category', + }, + }, + ], +}; + +describe('Quiz Entry Utils', () => { + beforeEach(async () => { + window.lana = { log: sinon.stub() }; + fetchStub = sinon.stub(window, 'fetch'); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve(mockData), + }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should handle the next flow of questions', async () => { + const nextQuestion = handleNext( + mockQuestionsData, + selectedQuestion, + userInputSelections, + userFlow, + ); + expect(nextQuestion).to.deep.equal(nextFlow); + }); + + it('should handle the next flow of questions with not()', async () => { + const nextQuestion = handleNext( + mockQuestionsData, + selectedQuestion, + userInputSelectionsNot, + userFlow, + ); + expect(nextQuestion).to.deep.equal(nextFlowNot); + }); + + it('should handle the next flow of questions with reset()', async () => { + const nextQuestion = handleNext( + mockQuestionsData, + selectedQuestion, + userInputSelectionsReset, + userFlow, + ); + expect(nextQuestion).to.deep.equal(nextFlowReset); + }); + + it('should fetch quiz data', async () => { + const [questions, strings] = await getQuizJson('./mocks/'); + expect(questions.questions).to.deep.equal(mockQuestionsData); + expect(strings.strings).to.deep.equal(mockStringsData); + }); +}); + +describe('Quiz Entry Utils failed request', () => { + beforeEach(async () => { + window.lana = { log: sinon.stub() }; + fetchStub = sinon.stub(window, 'fetch'); + fetchStub.resolves({ ok: false }); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should log an error when fetching quiz data fails', async () => { + await getQuizJson('./mocks/'); + expect(window.lana.log.called).to.be.true; + }); + it('should return nextSelections on handleSelections', async () => { + const nextSelections = handleSelections(prevSelections, selectedQuestion, selections); + expect(nextSelections).to.deep.equal(nextSelectionsExpected); + }); + + it('should de-dup any existing data if they use the ml field and cards.', async () => { + const prevSelectionsLength = [{ + selectedQuestion: { + 'max-selections': '3', + 'min-selections': '1', + questions: 'q-category', + }, + }]; + + const selectedQuestionPrev = [{ + selectedQuestion: { + 'max-selections': '3', + 'min-selections': '1', + questions: 'q-category', + }, + }]; + + const nextSelections = handleSelections(prevSelectionsLength, selectedQuestionPrev, selections); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(nextSelections).to.deep.equal(nextSelections); + }); + + it('should return quizPath, maxQuestions, analyticsQuiz, analyticsType, questionData', async () => { + document.body.innerHTML = await readFile({ path: './mocks/index.html' }); + const el = document.querySelector('.quiz-entry'); + + const quizEntryData = await getQuizEntryData(el); + expect(quizEntryData).to.deep.equal(quizConfig); + }); +});