diff --git a/cypress/index.d.ts b/cypress/index.d.ts index 2c9ce62..47d2195 100644 --- a/cypress/index.d.ts +++ b/cypress/index.d.ts @@ -12,6 +12,7 @@ declare namespace Cypress { visitWorksheetAdditionSubtraction(): Chainable; visitWorksheetVerticalAddition(): Chainable; visitWorksheetSubtractionWithFigures(): Chainable; + visitWorksheetSubtractionFillInTheBlanks(): Chainable; } interface Chainable { diff --git a/cypress/integration/addition_fill_the_blanks.ts b/cypress/integration/addition_fill_the_blanks.ts index 0b13ec2..8c33ce8 100644 --- a/cypress/integration/addition_fill_the_blanks.ts +++ b/cypress/integration/addition_fill_the_blanks.ts @@ -8,22 +8,22 @@ it('can create fill in the blanks addition worksheets', () => { cy.withinPreview(() => { cy.contains('2 + 3 = '); }); - cy.findByLabelText('Blank').select('Addends'); + cy.findByLabelText(/blank position/i).select('Addends'); cy.withinPreview(() => { cy.contains(/_ \+ [23] = [56]/); }); - cy.findByLabelText('Blank').select('Random'); + cy.findByLabelText(/blank position/i).select('Random'); cy.withinPreview(() => { cy.contains(/_ \+ [23] = [56]/); cy.contains(/[23] \+ _+ = [56]/); cy.contains(/[23] \+ [23] = _/); }); - cy.findByLabelText('Problem Generation').select('Custom Addends'); + cy.findByLabelText(/problem.+generation/i).select('Custom Addends'); cy.setNumberRange(/addend a/i, 2, 3); cy.setNumberRange(/addend b/i, 4, 5); - cy.findByLabelText('Blank').select('Sum'); + cy.findByLabelText(/blank position/i).select('Sum'); cy.findByLabelText(/number of problems/i).clearType('50'); cy.withinPreview(() => { cy.get('ol.problems').find('li').each(($li) => { diff --git a/cypress/integration/navigation.spec.ts b/cypress/integration/navigation.spec.ts index 49a3905..cf05579 100644 --- a/cypress/integration/navigation.spec.ts +++ b/cypress/integration/navigation.spec.ts @@ -44,6 +44,12 @@ it('can visit all subpages', () => { goBackHome(); + // Subtraction + clickWorksheetLink(/subtraction.+fill.+blank/i); + cy.hasCustomizeFormHeading(/subtraction.+fill.+blank/i); + + goBackHome(); + // Pattern Worksheets clickWorksheetLink(/patterns/i); cy.hasCustomizeFormHeading(/patterns/i); diff --git a/cypress/integration/save_printable_settings.ts b/cypress/integration/save_printable_settings.ts index 402b218..4c7eaf3 100644 --- a/cypress/integration/save_printable_settings.ts +++ b/cypress/integration/save_printable_settings.ts @@ -3,7 +3,7 @@ it('can save changes in settings', () => { cy.visitAdditionFillTheBlanks(); cy.findByLabelText(/number of problems/i).clearType('40'); cy.setNumberRange(/number range/i, 1, 5); - cy.findByLabelText('Blank').select('Addends'); + cy.findByLabelText(/blank.+position/i).select('Addends'); cy.visitCalendar(); cy.findByLabelText(/month/i).select('December'); @@ -14,7 +14,7 @@ it('can save changes in settings', () => { cy.findByLabelText(/number of problems/i).invoke('val').should('equal', '40'); cy.findByLabelText(/from/i).invoke('val').should('equal', '1'); cy.findByLabelText(/^to/i).invoke('val').should('equal', '5'); - cy.findByLabelText('Blank').invoke('val').should('equal', 'addends'); + cy.findByLabelText(/blank.+position/i).invoke('val').should('equal', 'addends'); cy.visitCalendar(); cy.findByLabelText(/month/i).invoke('val').should('equal', '11'); diff --git a/cypress/integration/worksheet_subtraction_fill_in_the_blanks.ts b/cypress/integration/worksheet_subtraction_fill_in_the_blanks.ts new file mode 100644 index 0000000..540fdd1 --- /dev/null +++ b/cypress/integration/worksheet_subtraction_fill_in_the_blanks.ts @@ -0,0 +1,37 @@ +it('can create subtraction fill in the blanks worksheets', () => { + cy.visitWorksheetSubtractionFillInTheBlanks(); + + cy.findByLabelText(/count/i).clearType('15'); + + cy.findByLabelText(/problem.+generation/i).should('have.value', 'minuend'); + cy.findByLabelText(/blank position/i).select('Random'); + cy.setNumberRange(/minuend/i, 3, 4); + + cy.withinPreview(() => { + cy.contains(/[34]\s+-\s+[0-4]\s+=\s+_+/); + cy.contains(/_+\s+-\s+[0-4]\s+=\s+\d/); + }); + + cy.findByLabelText(/problem.+generation/i).select('Subtrahend and Difference'); + cy.setNumberRange(/subtrahend/i, 3, 4); + cy.setNumberRange(/difference/i, 5, 6); + cy.findByLabelText(/count/i).clearType('28'); + cy.findByLabelText(/columns/i).clearType('3'); + cy.findByLabelText(/blank position/i).select('Difference'); + + cy.withinPreview(() => { + // Problems + cy.problemListItems() + .should('have.length', 28) + .each(($li) => { + cy.wrap($li).contains(/([8-9]|10)\s+-\s+[3-6]\s+=\s+_+/); + }); + + // Answers + cy.answerListItems() + .should('have.length', 28) + .each(($li) => { + cy.wrap($li).contains(/([8-9]|10)\s+-\s+[3-6]\s+=\s+\d/); + }); + }); +}); diff --git a/cypress/integration/worksheet_subtraction_with_figures.ts b/cypress/integration/worksheet_subtraction_with_figures.ts index eebd6bd..cebfe72 100644 --- a/cypress/integration/worksheet_subtraction_with_figures.ts +++ b/cypress/integration/worksheet_subtraction_with_figures.ts @@ -27,7 +27,7 @@ it('can create subtraction with figures worksheets', () => { cy.answerListItems() .should('have.length', 30) .each(($li) => { - // TODO: Why does the following not work? + // TODO: Change it back when https://github.com/cypress-io/cypress/issues/21108 is fixed // cy.wrap($li).contains(/[6-8]\s+-\s+[23]\s+=\s+[45]/); cy.wrap($li).contains(/[6-8]\s+-\s+[23]\s+=\s+\d/); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 92e23e5..c94c5d3 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -37,6 +37,7 @@ const paths = { worksheetAdditionSubtraction: '/worksheet-addition-subtraction', worksheetVerticalAddition: '/worksheet-vertical-addition', worksheetSubtractionWithFigures: '/worksheet-subtraction-with-figures', + worksheetSubtractionFillInTheBlanks: '/worksheet-subtraction-fill-in-the-blanks', }; const pathNames = Object.keys(paths); diff --git a/src/components/forms/NumberRangeSlider.tsx b/src/components/forms/NumberRangeSlider.tsx index da82534..6162f06 100644 --- a/src/components/forms/NumberRangeSlider.tsx +++ b/src/components/forms/NumberRangeSlider.tsx @@ -171,7 +171,12 @@ function NumberRangeSlider(options: NumberRangeSliderProps): JSX.Element { {label} - + import('../pages/main/HomePage')); const CalendarPage = lazy(() => import('../pages/calendar/CalendarPage')); const AdditionFillTheBlanksPage = lazy(() => import('../pages/additionFillTheBlanks/AdditionFillTheBlanksPage')); +const VerticalAdditionPage = lazy(() => import('../pages/verticalAddition/VerticalAdditionPage')); const AdditionSubtractionPage = lazy(() => import('../pages/additionSubtraction/AdditionSubtractionPage')); const SubtractionWithFiguresPage = lazy(() => import('../pages/subtractionWithFigures/SubtractionWithFiguresPage')); -const VerticalAdditionPage = lazy(() => import('../pages/verticalAddition/VerticalAdditionPage')); +const SubtractionFillInTheBlanksPage = lazy(() => import('../pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksPage')); const PatternsPage = lazy(() => import('../pages/patterns/PatternsPage')); const PlaceValuesPage = lazy(() => import('../pages/placeValues/PlaceValuesPage')); const NumbersToWordsPage = lazy(() => import('../pages/numbersToWords/NumbersToWordsPage')); @@ -55,6 +56,10 @@ export const mathLinks: SectionLinks = new Map([ text: 'Subtraction with Figures', loader: SubtractionWithFiguresPage, }], + ['/worksheet-subtraction-fill-in-the-blanks', { + text: 'Subtraction: Fill in the Blanks', + loader: SubtractionFillInTheBlanksPage, + }], ['/worksheet-patterns', { text: 'Patterns', loader: PatternsPage, diff --git a/src/lib/math/generateSubtractionProblems.ts b/src/lib/math/generateSubtractionProblems.ts new file mode 100644 index 0000000..3ff4f65 --- /dev/null +++ b/src/lib/math/generateSubtractionProblems.ts @@ -0,0 +1,54 @@ +import { randomGenerator } from '../RandomNumberGenerator'; +import tryByKey from '../tryByKey'; +import Subtraction from './Subtraction'; +import Range from '../Range'; + +export type ProblemGeneration = 'minuend' | 'subtrahend and difference'; + +export interface SubtractionProblemsProps { + count: number; + problemGeneration: ProblemGeneration; + minuend: Range; + subtrahend: Range; + difference: Range; +} + +export function generateProblemsFromMinuend(minuendRange: Range, count: number): Subtraction[] { + const problems: Subtraction[] = []; + const limitedRetries = tryByKey(); + for (let i = 0; problems.length < count; i++) { + const { from, to } = minuendRange; + const minuend = randomGenerator.integer(to, from); + const subtrahend = randomGenerator.integer(minuend, 0); + limitedRetries(`${minuend}:${subtrahend}`, () => { + problems.push(Subtraction.create({ minuend, subtrahend })); + }); + } + return problems; +} + +export function generateProblemsFromSubAndDiff( + subRange: Range, + diffRange: Range, + count: number, +): Subtraction[] { + const problems: Subtraction[] = []; + const limitedRetries = tryByKey(); + for (let i = 0; problems.length < count; i++) { + const subtrahend = randomGenerator.integer(subRange.to, subRange.from); + const difference = randomGenerator.integer(diffRange.to, diffRange.from); + limitedRetries(`${subtrahend}:${difference}`, () => { + problems.push(Subtraction.create({ subtrahend, difference })); + }); + } + return problems; +} + +export default function generateSubtractionProblems({ + count, problemGeneration, minuend, subtrahend, difference, +}: SubtractionProblemsProps): Array { + if (problemGeneration === 'minuend') { + return generateProblemsFromMinuend(minuend, count); + } + return generateProblemsFromSubAndDiff(subtrahend, difference, count); +} diff --git a/src/pages/additionFillTheBlanks/CustomizeAftbForm.tsx b/src/pages/additionFillTheBlanks/CustomizeAftbForm.tsx index 21e62ec..36d6900 100644 --- a/src/pages/additionFillTheBlanks/CustomizeAftbForm.tsx +++ b/src/pages/additionFillTheBlanks/CustomizeAftbForm.tsx @@ -147,7 +147,7 @@ function CustomizeAftbForm({ } void; + data: SubtractionFillInTheBlanksData; +} + +function CustomizeSubtractionFillInTheBlanksForm({ + data, onChange, +}: CustomizeSubtractionFillInTheBlanksFormProps): JSX.Element { + const generationFields = data.problemGeneration === 'minuend' + ? ( + onChange({ ...data, minuend })} + /> + ) + : ( + <> + onChange({ ...data, subtrahend })} + /> + onChange({ ...data, difference })} + /> + + ); + return ( + + onChange({ ...data, count })} + /> + { + onChange({ + ...data, + problemGeneration: value as ProblemGeneration, + }); + }} + > + {stringMapToOptions(problemGenerationOptions)} + + + {generationFields} + + { + onChange({ + ...data, + blankPosition: value as BlankPosition, + }); + }} + > + {stringMapToOptions(blankPositions)} + + + + onChange({ ...data, columns })} + /> + onChange({ ...data, fontSize })} + /> + + ); +} + +export default CustomizeSubtractionFillInTheBlanksForm; diff --git a/src/pages/subtractionFillInTheBlanks/PreviewSubtractionFillInTheBlanks.tsx b/src/pages/subtractionFillInTheBlanks/PreviewSubtractionFillInTheBlanks.tsx new file mode 100644 index 0000000..80fe5d7 --- /dev/null +++ b/src/pages/subtractionFillInTheBlanks/PreviewSubtractionFillInTheBlanks.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import MultiPaperPage from '../../components/MultiPaperPage'; +import PageTitle from '../../elements/PageTitle'; +import ProblemList from '../../components/ProblemList'; +import WorksheetFooter from '../../components/WorksheetFooter'; +import WorksheetHeader from '../../components/WorksheetHeader'; +import SubtractionFillInTheBlanksData, { BlankPosition } from './SubtractionFillInTheBlanksData'; +import generateSubtractionProblems from '../../lib/math/generateSubtractionProblems'; +import Subtraction from '../../lib/math/Subtraction'; +import SubtractionSentence from '../subtractionWithFigures/SubtractionSentence'; +import { SubtractionBlankPosition, subtractionBlankPositions } from '../../components/math/SubtractionSentenceBasic'; +import randomElement from '../../lib/randomElement'; + +interface SubtractionMeta { + subtraction: Subtraction; + blank: SubtractionBlankPosition; +} + +function itemBuilder( + showAnswer: boolean, + fontSize: number, +) { + const keyPrefix = showAnswer ? 'answer' : 'problem'; + function fn({ subtraction, blank }: SubtractionMeta, indexNumber: number) { + return ( + + ); + } + return fn; +} + +function getBlank(strategy: BlankPosition): SubtractionBlankPosition { + if (strategy === 'random') { + return randomElement(subtractionBlankPositions); + } + return strategy; +} + +function generateProblems(data: SubtractionFillInTheBlanksData): SubtractionMeta[] { + return generateSubtractionProblems(data).map((subtraction) => ({ + subtraction, + blank: getBlank(data.blankPosition), + })); +} + +interface PreviewSubtractionFillInTheBlanksProps { + data: SubtractionFillInTheBlanksData; +} + +function PreviewSubtractionFillInTheBlanks({ + data, +}: PreviewSubtractionFillInTheBlanksProps): JSX.Element { + const problems = generateProblems(data); + const { columns, fontSize } = data; + const instructions = 'Complete the subtraction facts by filling in the blanks.'; + + return ( + <> + +

{instructions}

+ + )} + wrapper={ProblemList} + footer={()} + wrapperProps={{ + className: 'problems', + columns, + }} + data={problems} + itemSelector=".subtraction-fill-in-the-blanks-problem-item" + renderItems={itemBuilder(false, fontSize)} + /> + Answer Key + )} + wrapper={ProblemList} + wrapperProps={{ + className: 'answers', + label: 'Answers', + columns, + }} + data={problems} + itemSelector=".subtraction-fill-in-the-blanks-problem-item" + renderItems={itemBuilder(true, fontSize)} + /> + + ); +} + +export default PreviewSubtractionFillInTheBlanks; diff --git a/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksData.ts b/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksData.ts new file mode 100644 index 0000000..568f185 --- /dev/null +++ b/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksData.ts @@ -0,0 +1,27 @@ +import { SubtractionBlankPosition } from '../../components/math/SubtractionSentenceBasic'; +import Range from '../../lib/Range'; + +export type BlankPosition = SubtractionBlankPosition | 'random'; +export const blankPositions = new Map([ + ['difference', 'Difference'], + ['minuend', 'Minuend'], + ['subtrahend', 'Subtrahend'], + ['random', 'Random'], +]); + +export type ProblemGeneration = 'minuend' | 'subtrahend and difference'; +export const problemGenerationOptions = new Map([ + ['minuend', 'Minuend'], + ['subtrahend and difference', 'Subtrahend and Difference'], +]); + +export default interface SubtractionFillInTheBlanksData { + count: number; + problemGeneration: ProblemGeneration; + minuend: Range; + subtrahend: Range; + difference: Range; + blankPosition: BlankPosition; + columns: number; + fontSize: number; +} diff --git a/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksPage.tsx b/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksPage.tsx new file mode 100644 index 0000000..719ae3a --- /dev/null +++ b/src/pages/subtractionFillInTheBlanks/SubtractionFillInTheBlanksPage.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import PrintableUI from '../../components/PrintableUI'; +import usePageState from '../usePageState'; +import CustomizeSubtractionFillInTheBlanksForm from './CustomizeSubtractionFillInTheBlanksForm'; +import SubtractionFillInTheBlanksData from './SubtractionFillInTheBlanksData'; +import PreviewSubtractionFillInTheBlanks from './PreviewSubtractionFillInTheBlanks'; + +const defaultData: SubtractionFillInTheBlanksData = { + count: 10, + problemGeneration: 'minuend', + minuend: { from: 0, to: 9 }, + subtrahend: { from: 0, to: 9 }, + difference: { from: 0, to: 9 }, + blankPosition: 'difference', + columns: 2, + fontSize: 20, +}; +const key = 'subtractionFillInTheBlanks'; + +function SubtractionFillInTheBlanksPage(): JSX.Element { + const { data, onChange } = usePageState({ + key, defaultData, + }); + return ( + + )} + > + + + ); +} + +export default SubtractionFillInTheBlanksPage; diff --git a/src/pages/subtractionWithFigures/PreviewSubtractionWithFigures.tsx b/src/pages/subtractionWithFigures/PreviewSubtractionWithFigures.tsx index 61f2e81..2888708 100644 --- a/src/pages/subtractionWithFigures/PreviewSubtractionWithFigures.tsx +++ b/src/pages/subtractionWithFigures/PreviewSubtractionWithFigures.tsx @@ -5,54 +5,10 @@ import ProblemList from '../../components/ProblemList'; import WorksheetFooter from '../../components/WorksheetFooter'; import WorksheetHeader from '../../components/WorksheetHeader'; import SubtractionWithFiguresData from './SubtractionWithFiguresData'; -import Range from '../../lib/Range'; import Subtraction from '../../lib/math/Subtraction'; -import { randomGenerator } from '../../lib/RandomNumberGenerator'; import SubtractionSentence from './SubtractionSentence'; import SubtractionFigure from './SubtractionFigure'; -import tryByKey from '../../lib/tryByKey'; - -function generateProblemsFromMinuend(minuendRange: Range, count: number): Subtraction[] { - const problems: Subtraction[] = []; - const limitedRetries = tryByKey(); - for (let i = 0; problems.length < count; i++) { - const { from, to } = minuendRange; - const minuend = randomGenerator.integer(to, from); - const subtrahend = randomGenerator.integer(minuend, 0); - limitedRetries(`${minuend}:${subtrahend}`, () => { - problems.push(Subtraction.create({ minuend, subtrahend })); - }); - } - return problems; -} - -function generateProblemsFromSubAndDiff( - subRange: Range, - diffRange: Range, - count: number, -): Subtraction[] { - const problems: Subtraction[] = []; - const limitedRetries = tryByKey(); - for (let i = 0; problems.length < count; i++) { - const subtrahend = randomGenerator.integer(subRange.to, subRange.from); - const difference = randomGenerator.integer(diffRange.to, diffRange.from); - limitedRetries(`${subtrahend}:${difference}`, () => { - problems.push(Subtraction.create({ subtrahend, difference })); - }); - } - return problems; -} - -function generateProblems( - { - count, problemGeneration, minuend, subtrahend, difference, - }: SubtractionWithFiguresData, -): Array { - if (problemGeneration === 'minuend') { - return generateProblemsFromMinuend(minuend, count); - } - return generateProblemsFromSubAndDiff(subtrahend, difference, count); -} +import generateSubtractionProblems from '../../lib/math/generateSubtractionProblems'; function itemBuilder( showAnswer: boolean, @@ -81,7 +37,7 @@ interface PreviewSubtractionWithFiguresProps { } function PreviewSubtractionWithFigures({ data }: PreviewSubtractionWithFiguresProps): JSX.Element { - const problems = generateProblems(data); + const problems = generateSubtractionProblems(data); const { columns } = data; const instructions = 'Cross-out the necessary number of circles to help you find the answer in the blanks.'; diff --git a/src/pages/subtractionWithFigures/SubtractionSentence.tsx b/src/pages/subtractionWithFigures/SubtractionSentence.tsx index 009761a..891927f 100644 --- a/src/pages/subtractionWithFigures/SubtractionSentence.tsx +++ b/src/pages/subtractionWithFigures/SubtractionSentence.tsx @@ -3,13 +3,14 @@ import SubtractionSentenceBasic from '../../components/math/SubtractionSentenceB import ProblemListItem from '../../components/ProblemListItem'; import Subtraction from '../../lib/math/Subtraction'; -type SubtractionBlankPosition = 'difference' | 'minuend' | 'subtrahend'; +export type SubtractionBlankPosition = 'difference' | 'minuend' | 'subtrahend'; interface SubtractionSentenceProps { subtraction: Subtraction; blank?: SubtractionBlankPosition; showAnswer: boolean; prefix?: JSX.Element | string; + fontSize?: number; } function SubtractionSentence({ @@ -17,11 +18,13 @@ function SubtractionSentence({ blank = 'difference', showAnswer = false, prefix, + fontSize = 20, }: SubtractionSentenceProps): JSX.Element { return ( {prefix}