Skip to content

Commit

Permalink
Added: Add 2x3 grid support for Matching Pairs component if it has eq…
Browse files Browse the repository at this point in the history
…ual or less than 6 sections (#657)

* refactor: Update scss-watch command to be scss:watch

* feat: Add classNames utility function

This commit adds a utility function called `classNames` to the `util` directory of the frontend code. The `classNames` function takes in multiple string arguments and returns a single string with the classes joined together, separated by a space. It also filters out any falsy values before joining the classes.

The commit also includes a test suite for the `classNames` function, covering scenarios where multiple classes are joined, falsy values are filtered, and empty strings and no arguments are provided.

* feat: Update MatchingPairs column amount behavior

- Add condition to determine column count based on the number of sections in the code.
- Update CSS styles for the playing board based on column count.
- Remove unnecessary white spaces and newlines in the code.

* story: Add MatchingPairs story with default props and two columns support

This commit adds the MatchingPairs component with its default props and support for displaying two columns of sections. The component automatically adjusts the number of columns based on the number of sections provided. If there are six or less sections, two columns are displayed. If there are more than six sections, four columns are displayed. The component is also displayed in fullscreen mode.

* test: Add tests for the MatchingPairs component

- Added tests for the MatchingPairs component to ensure it properly displays two columns when the sections length is less than or equal to 6, and four columns when the sections length is greater than 6. The tests use a mock PlayCard component and check for the presence or absence of specific CSS classes on the playing board.

* refactor: MatchingPairs displays 3 columns instead of 2 when sections <= 6

* config: Add storybook script and update permissions on test-front-ci

* fix: Fix classes & story by actually showing 3 columns instead of 2
  • Loading branch information
drikusroor authored Dec 18, 2023
1 parent 7276f39 commit 10962ef
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 42 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"test:ci": "react-scripts test --coverage --watchAll=false",
"eject": "react-scripts eject",
"scss": "sass src/index.scss src/index.css",
"scss-watch": "sass src/index.scss src/index.css; sass --watch src/index.scss src/index.css",
"scss:watch": "sass src/index.scss src/index.css; sass --watch src/index.scss src/index.css",
"storybook": "REACT_APP_API_ROOT=http://localhost:8000 && storybook dev -p 6006",
"storybook:build": "storybook build",
"lint:ts": "eslint . --ext .js,.jsx,.ts,.tsx",
Expand Down
71 changes: 36 additions & 35 deletions frontend/src/components/Playback/MatchingPairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const MatchingPairs = ({
finishedPlaying,
stopAudioAfter,
submitResult,
}) => {
}) => {
const xPosition = useRef(-1);
const yPosition = useRef(-1);
const score = useRef(undefined);
Expand All @@ -19,13 +19,14 @@ const MatchingPairs = ({
const [total, setTotal] = useState(100);
const [message, setMessage] = useState('Pick a card')
const [end, setEnd] = useState(false);
const columnCount = sections.length > 6 ? 4 : 3;

const resultBuffer = useRef([]);

const startTime = useRef(Date.now());

const setScoreMessage = (score) => {
switch (score) {
switch (score) {
case -10: return '-10 <br />Misremembered';
case 0: return '0 <br />No match';
case 10: return '+10 <br />Lucky match';
Expand All @@ -44,62 +45,62 @@ const MatchingPairs = ({
}

// Show (animated) feedback after second click on second card or finished playing
const showFeedback = () => {
const showFeedback = () => {

const turnedCards = sections.filter(s => s.turned);
// Check if this turn has finished
if (turnedCards.length === 2) {
if (turnedCards.length === 2) {
// update total score & display current score
setTotal(total+score.current);
setMessage(setScoreMessage(score.current));
setMessage(setScoreMessage(score.current));
// show end of turn animations
switch (score.current) {
switch (score.current) {
case 10:
turnedCards[0].lucky = true;
turnedCards[1].lucky = true;
turnedCards[1].lucky = true;
break;
case 20:
turnedCards[0].memory = true;
turnedCards[1].memory = true;
turnedCards[1].memory = true;
break;
default:
turnedCards[0].nomatch = true;
turnedCards[1].nomatch = true;
// reset nomatch cards for coming turns
setTimeout(() => {
turnedCards[0].nomatch = false;
turnedCards[1].nomatch = false;
turnedCards[1].nomatch = false;
}, 700);
break;
}
break;
}

// add third click event to finish the turn
document.getElementById('root').addEventListener('click', finishTurn);
return;
}
}

const checkMatchingPairs = (index) => {
const checkMatchingPairs = (index) => {
const currentCard = sections[index];
const turnedCards = sections.filter(s => s.turned);
if (turnedCards.length < 2) {
if (turnedCards.length === 1) {
// We have two turned cards
currentCard.turned = true;
secondCard.current = index;
secondCard.current = index;
// set no mouse events for all but current
sections.forEach(section => section.noevents = true);
sections.forEach(section => section.noevents = true);
currentCard.noevents = true;
// check for match
const lastCard = sections[firstCard.current];
const lastCard = sections[firstCard.current];
if (lastCard.group === currentCard.group) {
// match
// match
if (currentCard.seen) {
score.current = 20;
score.current = 20;
} else {
score.current = 10;
score.current = 10;
}
} else {
} else {
if (currentCard.seen) { score.current = -10; }
else { score.current = 0; }
};
Expand All @@ -114,8 +115,8 @@ const MatchingPairs = ({
currentCard.noevents = true;
// clear message
setMessage('');
}
resultBuffer.current.push({
}
resultBuffer.current.push({
selectedSection: currentCard.id,
cardIndex: index,
score: score.current,
Expand All @@ -128,9 +129,9 @@ const MatchingPairs = ({
const finishTurn = () => {
finishedPlaying();
// remove matched cards from the board
if (score.current === 10 || score.current === 20) {
if (score.current === 10 || score.current === 20) {
sections[firstCard.current].inactive = true;
sections[secondCard.current].inactive = true;
sections[secondCard.current].inactive = true;
}
firstCard.current = -1;
secondCard.current = -1;
Expand All @@ -139,12 +140,12 @@ const MatchingPairs = ({
score.current = undefined;
// Turn all cards back and enable events
sections.forEach(section => section.turned = false);
sections.forEach(section => section.noevents = false);
sections.forEach(section => section.noevents = false);
// Check if the board is empty
if (sections.filter(s => s.inactive).length === sections.length) {
// all cards have been turned
setEnd(true);
} else { setMessage(''); }
setEnd(true);
} else { setMessage(''); }
}

if (end) {
Expand All @@ -157,17 +158,17 @@ const MatchingPairs = ({
<div className="col-6 align-self-start">
<div dangerouslySetInnerHTML={{ __html: message }}
className={classNames("matching-pairs__feedback", {fbnomatch: score.current === 0}, {fblucky: score.current === 10}, {fbmemory: score.current === 20}, {fbmisremembered: score.current === -10})}

/>
</div>
<div className="col-6 align-self-end">
<div className="matching-pairs__score">Score: <br />{total}</div>
<div className="matching-pairs__score">Score: <br />{total}</div>
</div>
</div>

<div className="playing-board">
<div className={classNames("playing-board", columnCount === 3 && "playing-board--three-columns")}>
{Object.keys(sections).map((index) => (
<PlayCard
<PlayCard
key={index}
onClick={()=> {
playSection(index);
Expand All @@ -177,13 +178,13 @@ const MatchingPairs = ({
playing={playerIndex === index}
section={sections[index]}
onFinish={showFeedback}
stopAudioAfter={stopAudioAfter}
stopAudioAfter={stopAudioAfter}
/>
)
)}
</div>
</div>
</div>
)
}

export default MatchingPairs;
export default MatchingPairs;
20 changes: 14 additions & 6 deletions frontend/src/components/Playback/MatchingPairs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
position: absolute;
left: 50%;
transform: translateX(-50%);
margin: 0px auto;
margin: 0px auto;
}

@media (min-aspect-ratio: 1/1) {
.playing-board {
.playing-board {
grid-template-columns: 16vh 16vh 16vh 16vh;
column-gap: 1.5vh;
row-gap: 1.5vh;

&.playing-board--three-columns {
grid-template-columns: 16vh 16vh 16vh;
}
}
}

Expand All @@ -32,12 +36,16 @@
grid-template-columns: 18vw 18vw 18vw 18vw;
column-gap: 2vw;
row-gap: 2vw;

&.playing-board--three-columns {
grid-template-columns: 18vw 18vw 18vw;
}
}
}

.playing-board {
display: inline-grid;
max-width: 100%;
max-width: 100%;
}

.matching-pairs__feedback, .matching-pairs__score {
Expand All @@ -57,7 +65,7 @@
.matching-pairs__feedback {
float: left;
color: white;

&.fbnomatch {
color: $gray;
}
Expand All @@ -70,8 +78,8 @@
&.fbmisremembered {
color: $red;
}
}
}

.matching-pairs__score {
float: right;
}
}
30 changes: 30 additions & 0 deletions frontend/src/components/Playback/MatchingPairs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { render } from '@testing-library/react';
import MatchingPairs from './MatchingPairs';

jest.mock("../PlayButton/PlayCard", () => (props) => (
<div data-testid="play-card" {...props} />
));

describe('MatchingPairs Component', () => {

const baseProps = {
playSection: jest.fn(),
playerIndex: 0,
finishedPlaying: jest.fn(),
stopAudioAfter: jest.fn(),
submitResult: jest.fn(),
};

it('displays three columns when sections length is less than or equal to 6', () => {
const sections = new Array(6).fill({}).map((_, index) => ({ id: index }));
const { container } = render(<MatchingPairs {...baseProps} sections={sections} />);
expect(container.querySelector('.playing-board--three-columns')).toBeInTheDocument();
});

it('displays four columns when sections length is greater than 6', () => {
const sections = new Array(7).fill({}).map((_, index) => ({ id: index }));
const { container } = render(<MatchingPairs {...baseProps} sections={sections} />);
expect(container.querySelector('.playing-board--two-columns')).not.toBeInTheDocument();
});
});
Loading

0 comments on commit 10962ef

Please sign in to comment.