Skip to content

Commit

Permalink
MWPW-148253 Quiz Entry Coverage (#2305)
Browse files Browse the repository at this point in the history
* Initial quiz-entry block with ml field

* MWPW-144810: Quiz Entry - Add option cards and text to the block (#2095)

* Quiz entry block (#2103)

* MWPW-144810: Quiz Entry - Add option cards and text to the block

* MWPW-144810: Add the text to strings.xlsx for title, subtitle, ML field default, card instruction bar and the button

* MWPW-146243 - Quiz entry code optimization (#2121)

* MWPW-146243 - Quiz entry code optimization

* Restores code optimization that was lost in previous PRs

Resolves: [MWPW-146243](https://jira.corp.adobe.com/browse/MWPW-146243)

* Update utils.js

* Update quiz-entry.js

Set button to use string values

* Update quiz-entry.js

Fixed to use new strings object

* MWPW-146034 - Quiz entry block accessibility (#2139)

* resolved accessibility concerns when the ml input is used
* general code refinements for more clarity, specifically for getting string values

Resolves: [MWPW-146034](https://jira.corp.adobe.com/browse/MWPW-146034)

* MWPW-146036 - Rig up quiz entry button (#2190)

* Support for ml filtering
* Debug support using ?debug=quiz-entry
* Store quizState in local storage and redirect to the quiz

Resolves: [MWPW-146036](https://jira.corp.adobe.com/browse/MWPW-146036)

* Quiz entry block (#2204)

* MWPW-144810: Quiz Entry - Add option cards and text to the block

* MWPW-144810: Add the text to strings.xlsx for title, subtitle, ML field default, card instruction bar and the button

* MWPW-147031:Add Analytics for Quiz Entry Block, MWPW-146080: Integrate Auto-Complete for Open Text Field on UAR Exposed Front Door

* fix eslint

* fix code according feedbacks

* MWPW-144022: Quiz entry block (#2227)

* carousel starts

* got buttons working

* MWPW-144022: Prototype carousel refinement, keybord controls updated

* linting fixes

* MWPW-147482 - ML Input Bulletproofing (#2242)

* add support for fallback fi codes
* add lana logging for ml field failures
* fix so the redirect checks for maxQuestions

Resolves: [MWPW-147482](https://jira.corp.adobe.com/browse/MWPW-147482)

**Test URLs:**
- Before: https://main--milo--adobecom.hlx.page/?martech=off
- After: https://<branch>--milo--adobecom.hlx.page/?martech=off

* MWPW-147683 - CSS Cleanup (#2267)

* MWPW-147683 - CSS Cleanup

* Final pass on the css
* Edits to markup where necessary

Resolves: [MWPW-147683](https://jira.corp.adobe.com/browse/MWPW-147683)

* pr feedback for vars and eslint errors

* more PR feedback on icon placement

* pr feedback for button border

* cleaned up the carousel widths so it's consistent it all times as it was previously 8px short.

* pr feedback on the location of the input clear X.

* PR feedback - reduced border on input, fixed a card disable bug, addressed card layout in tablet

* carousel starts

* got buttons working

* MWPW-144022: Prototype carousel refinement, keybord controls updated

* linting fixes

* Tests

* working tests update

* linting

* linting

* Update quiz-entry.js with default vals

* Bring back debug

* debugging debug

* Improved test coverage

* resolve conflicts

* restore style

* formatting fix

---------

Co-authored-by: Cody Lloyd <colloyd@adobe.com>
Co-authored-by: Jacky Sun <67350368+JackySun9@users.noreply.github.com>
Co-authored-by: Cody Lloyd <119891065+colloyd@users.noreply.github.com>
  • Loading branch information
4 people authored May 15, 2024
1 parent 7c47541 commit ed79630
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 12 deletions.
8 changes: 7 additions & 1 deletion libs/blocks/quiz-entry/quiz-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({});
Expand Down
7 changes: 0 additions & 7 deletions libs/blocks/quiz-entry/quizoption.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div class="quiz-options-container" role="group" aria-labelledby="question" tabindex="0" onkeydown=${handleKey}>
${index > 0 && html`<button onClick=${prev} class="carousel-arrow arrow-prev ${isRTL ? 'rtl' : ''}"></button>`}
Expand Down
5 changes: 4 additions & 1 deletion libs/blocks/quiz-entry/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions test/blocks/quiz-entry/mocks/mock-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
{
Expand Down Expand Up @@ -664,7 +664,7 @@ const resultsMock = {
},
{
options: 'video',
next: 'q-rather,q-video',
next: 'RESET',
type: 'card',
endpoint: '',
'api-key': '',
Expand Down
145 changes: 144 additions & 1 deletion test/blocks/quiz-entry/quiz-entry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
});
});
149 changes: 149 additions & 0 deletions test/blocks/quiz-entry/utils.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});

0 comments on commit ed79630

Please sign in to comment.