Skip to content

Commit

Permalink
feat: blanks addends
Browse files Browse the repository at this point in the history
Fixes: Can have blanks in addends #17
  • Loading branch information
asartalo committed Aug 9, 2021
1 parent 9b67181 commit 2c6b864
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 82 deletions.
11 changes: 11 additions & 0 deletions cypress/integration/fill_the_blanks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ it('can create fill in the blanks addition worksheets', () => {
cy.withinPreview(() => {
cy.contains('2 + 3 = ');
});
cy.findByLabelText('Blank').select('Addends');
cy.withinPreview(() => {
cy.contains(/_ \+ [23] = [56]/);
});

cy.findByLabelText('Blank').select('Random');
cy.withinPreview(() => {
cy.contains(/_ \+ [23] = [56]/);
cy.contains(/[23] \+ _+ = [56]/);
cy.contains(/[23] \+ [23] = _/);
});
});
2 changes: 1 addition & 1 deletion src/components/MultiPaperPage.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('MultiPaperPage', () => {
<MultiPaperPage<string>
wrapper="div"
wrapperProps={{ className: 'foo' }}
wrapperPropsInstanceCallback={propsCallback}
wrapperPropsCallback={propsCallback}
data={['a', 'b', 'c', 'd']}
itemSelector="p"
builder={
Expand Down
22 changes: 12 additions & 10 deletions src/components/MultiPaperPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ type PropsCallbackOptions = {
memberIndex: number,
};

type Props = Record<string, unknown>;
export type Builder<T> = (item: T, index: number, array: T[] | undefined) => JSX.Element;

export type Props = Record<string, unknown>;
export type PropsCallback = (props: Props, options: PropsCallbackOptions) => Props;
interface WrapperBuilder<T> {
wrapper?: ElementType | null;
wrapperProps?: Record<string, unknown>;
wrapperPropsInstanceCallback?: PropsCallback;
wrapperProps?: Props;
wrapperPropsCallback?: PropsCallback;
data: T[];
builder: (item: T, index: number, array: T[] | undefined) => JSX.Element;
builder: Builder<T>;
}
interface WrapperBuilderArgs<T> extends WrapperBuilder<T> {
wrapperPropsInstanceCallback: PropsCallback;
wrapperPropsCallback: PropsCallback;
instanceIndex: number;
memberIndex: number;
}
Expand All @@ -32,7 +34,7 @@ interface MultiPaperPageProps<T> extends WrapperBuilder<T> {
}

function wrappedContent<T>({
wrapper, wrapperProps, wrapperPropsInstanceCallback, data, builder,
wrapper, wrapperProps, wrapperPropsCallback, data, builder,
instanceIndex, memberIndex,
}: WrapperBuilderArgs<T>): ReactNode {
return wrapper === null
Expand All @@ -41,7 +43,7 @@ function wrappedContent<T>({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
wrapper!,
{
...wrapperPropsInstanceCallback(
...wrapperPropsCallback(
wrapperProps || {},
{ instanceIndex, memberIndex },
),
Expand Down Expand Up @@ -88,7 +90,7 @@ function isContentOverflowing(element: HTMLDivElement | null): boolean {
function MultiPaperPage<T>({
header = null, footer = null,
wrapper, wrapperProps = {}, data, builder,
wrapperPropsInstanceCallback = passThrough,
wrapperPropsCallback = passThrough,
itemSelector,
}: MultiPaperPageProps<T>): JSX.Element {
const wrapperRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -161,7 +163,7 @@ function MultiPaperPage<T>({
wrappedContent({
wrapper,
wrapperProps,
wrapperPropsInstanceCallback: wrapperPropsInstanceCallback || passThrough,
wrapperPropsCallback: wrapperPropsCallback || passThrough,
data: dataPage,
builder,
instanceIndex: index,
Expand All @@ -185,7 +187,7 @@ MultiPaperPage.defaultProps = {
footer: null,
wrapper: null,
wrapperProps: {},
wrapperPropsInstanceCallback: passThrough,
wrapperPropsCallback: passThrough,
};

export default MultiPaperPage;
2 changes: 2 additions & 0 deletions src/lib/RandomNumberGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ class RandomNumberGenerator implements NumberGenerator {
}
}

export const defaultGenerator = new RandomNumberGenerator(Math.random);

export default RandomNumberGenerator;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const AdditionFillTheBlanksPage = (): JSX.Element => {
rangeFrom: 0,
rangeTo: 9,
problems: 20,
blankStrategy: 'sum',
}));
const onPrint = () => true;
const onChange = (data: AftbData): void => {
Expand Down
68 changes: 68 additions & 0 deletions src/pages/additionFillTheBlanks/AdditionSentence.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { render } from '@testing-library/react';
import AdditionSentence, { BlankPosition } from './AdditionSentence';
import Addition from './Addition';

describe('AdditionSentence', () => {
const element = () => document.querySelector('.addition-sentence-item');

describe('default behavior', () => {
beforeEach(() => {
const addition = new Addition(1, 2);
return render(
<AdditionSentence addition={addition} />,
);
});

it('renders addition sentence', () => {
expect(element()).toHaveTextContent(/1 \+ 2 = _/);
});
});

describe('when showAnswer is true', () => {
beforeEach(() => render(
<AdditionSentence showAnswer addition={new Addition(3, 4)} />,
));

it('renders the answer instead of blank', () => {
expect(element()).toHaveTextContent(/3 \+ 4 = 7/);
});
});

describe('blanks', () => {
const blanks: Record<BlankPosition, RegExp> = {
addendA: /_+ \+ 6 = 11/,
addendB: /5 \+ _+ = 11/,
sum: /5 \+ 6 = _+/,
};

Object.entries(blanks).forEach(([blank, content]) => {
describe(`when blank=${blank}`, () => {
beforeEach(() => render(
<AdditionSentence
blank={blank as BlankPosition}
addition={new Addition(5, 6)}
/>,
));

it('blanks correct position', () => {
expect(element()).toHaveTextContent(content);
});
});

describe(`when blank=${blank} but showAnswer=true`, () => {
beforeEach(() => render(
<AdditionSentence
showAnswer
blank={blank as BlankPosition}
addition={new Addition(5, 6)}
/>,
));

it('displays everything when show answer is enabled', () => {
expect(element()).toHaveTextContent(/5 \+ 6 = 11/);
});
});
});
});
});
52 changes: 37 additions & 15 deletions src/pages/additionFillTheBlanks/AdditionSentence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,57 @@ export function generateAdditionSentences(
}
return generated;
}
interface AdditionSentenceProps {

export type BlankPosition = 'addendA' | 'addendB' | 'sum';
export const blankTypes: BlankPosition[] = ['addendA', 'addendB', 'sum'];
export const blankTypesAddends: BlankPosition[] = ['addendA', 'addendB'];
export interface AdditionSentenceProps {
addition: Addition;
showAnswer?: boolean;
blank?: BlankPosition;
}

const blankElement = (<span className="blank">___</span>);
interface BlankOrNumberProps {
expected: BlankPosition;
value: number;
}

function blankOrNumberGenerator(blank: BlankPosition, showAnswer: boolean) {
return ({ value, expected }: BlankOrNumberProps): JSX.Element => (
showAnswer || blank !== expected
? <span>{value}</span>
: blankElement
);
}

function AdditionSentence({
addition, showAnswer = false,
addition, blank = 'sum', showAnswer = false,
}: AdditionSentenceProps): JSX.Element {
const BlankOrNumber = blankOrNumberGenerator(blank, showAnswer);
return (
<li className="addition-sentence-item">
{addition.addendA}
{' '}
+
{' '}
{addition.addendB}
{' '}
=
{' '}
{
showAnswer
? addition.sum()
: <span className="blank">___</span>
}
<BlankOrNumber
value={addition.addendA}
expected="addendA"
/>
{' + '}
<BlankOrNumber
value={addition.addendB}
expected="addendB"
/>
{' = '}
<BlankOrNumber
value={addition.sum()}
expected="sum"
/>
</li>
);
}

AdditionSentence.defaultProps = {
showAnswer: false,
blank: 'sum',
};

export default AdditionSentence;
2 changes: 2 additions & 0 deletions src/pages/additionFillTheBlanks/AftbData.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export type BlankPositionStrategy = 'sum' | 'addends' | 'random';
export default interface AftbData {
rangeFrom: number;
rangeTo: number;
problems: number;
blankStrategy: BlankPositionStrategy;
}
55 changes: 47 additions & 8 deletions src/pages/additionFillTheBlanks/CustomizeAftbForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useState, ChangeEvent } from 'react';
import { TextField } from '@material-ui/core';
import { Select, TextField } from '@material-ui/core';
import CustomizeForm from '../../components/forms/CustomizeForm';
import AftbData from './AftbData';
import AftbData, { BlankPositionStrategy } from './AftbData';
import FieldSet from '../../components/forms/FieldSet';

export interface CustomizeAftbFormProps {
Expand All @@ -15,6 +15,12 @@ function numberOrEmpty(value: unknown): number | string {
return Number.isNaN(value) ? '' : value as number;
}

const blankTypesStrategies = new Map<BlankPositionStrategy, string>([
['sum', 'Sum'],
['addends', 'Addends'],
['random', 'Random'],
]);

const CustomizeAftbForm = ({
onBeforePrint, onChange,
initialData,
Expand All @@ -31,12 +37,7 @@ const CustomizeAftbForm = ({
return null;
};

const changeHandler = (field: string) => (event: ChangeEvent<{ value: unknown, }>) => {
const value = Number.parseInt(event.target.value as string, 10);
const updated = {
...data,
[field]: value,
};
const updateData = (updated: AftbData): void => {
const error = validate(updated);
if (error !== errorMessage) {
setErrorMessage(error);
Expand All @@ -47,6 +48,24 @@ const CustomizeAftbForm = ({
setData(updated);
};

const changeBlankStrategy = (event: ChangeEvent<{ value: unknown }>) => {
const strategy = event.target.value as BlankPositionStrategy;

updateData({
...data,
blankStrategy: strategy,
});
};

const changeHandler = (field: string) => (event: ChangeEvent<{ value: unknown, }>) => {
const value = Number.parseInt(event.target.value as string, 10);
const updated = {
...data,
[field]: value,
};
updateData(updated);
};

return (
<CustomizeForm
onBeforePrint={() => onBeforePrint(data)}
Expand Down Expand Up @@ -98,6 +117,26 @@ const CustomizeAftbForm = ({
onChange={changeHandler('rangeTo')}
/>
</FieldSet>
<FieldSet
label="Blank"
id="select-blank-position-strategy"
>
<Select
native
name="blankStrategy"
id="select-blank-position-strategy"
fullWidth
variant="filled"
value={data.blankStrategy}
onChange={changeBlankStrategy}
>
{
Array.from(blankTypesStrategies.entries()).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))
}
</Select>
</FieldSet>
</CustomizeForm>
);
};
Expand Down
Loading

0 comments on commit 2c6b864

Please sign in to comment.