diff --git a/package.json b/package.json index 2a4417ca32..469a289660 100644 --- a/package.json +++ b/package.json @@ -89,27 +89,27 @@ "@jest/test-sequencer": "29.7.0", "@types/d3-array": "3.0.3", "@pie-lib/categorize": "2.0.2", - "@pie-lib/charting": "7.0.11", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/charting": "7.0.12", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/controller-utils": "2.0.3", "@pie-lib/correct-answer-toggle": "4.0.5", "@pie-lib/drag": "4.0.6", "@pie-lib/editable-html": "13.0.7", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/feedback": "2.0.2", - "@pie-lib/graphing-solution-set": "4.0.11", + "@pie-lib/graphing-solution-set": "4.0.12", "@pie-lib/graphing-utils": "3.0.2", - "@pie-lib/graphing": "4.0.12", + "@pie-lib/graphing": "4.0.13", "@pie-lib/icons": "4.0.3", - "@pie-lib/mask-markup": "3.0.11", + "@pie-lib/mask-markup": "3.0.12", "@pie-lib/math-evaluator": "4.0.2", "@pie-lib/math-input": "8.1.1", "@pie-lib/math-rendering-accessible": "5.0.3", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/math-toolbar": "3.0.6", - "@pie-lib/plot": "4.0.11", + "@pie-lib/plot": "4.0.12", "@pie-lib/render-ui": "6.1.3", - "@pie-lib/rubric": "2.0.11", + "@pie-lib/rubric": "2.0.12", "@pie-lib/scoring-config": "5.0.3", "@pie-lib/style-utils": "2.0.2", "@pie-lib/test-utils": "2.0.2", diff --git a/packages/boilerplate-item-type/configure/package.json b/packages/boilerplate-item-type/configure/package.json index 54312d0f39..e719424b14 100644 --- a/packages/boilerplate-item-type/configure/package.json +++ b/packages/boilerplate-item-type/configure/package.json @@ -11,8 +11,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/calculator/configure/package.json b/packages/calculator/configure/package.json index d740812c39..56ad02c1e3 100644 --- a/packages/calculator/configure/package.json +++ b/packages/calculator/configure/package.json @@ -11,7 +11,7 @@ "@mui/material": "^7.3.4", "@pie-framework/material-ui-calculator": "4.0.0", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "react": "18.3.1", "react-dom": "18.3.1" }, diff --git a/packages/categorize/configure/package.json b/packages/categorize/configure/package.json index 8bf9b0e023..9f1c0974b2 100644 --- a/packages/categorize/configure/package.json +++ b/packages/categorize/configure/package.json @@ -16,9 +16,9 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", "@pie-lib/categorize": "2.0.2", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/drag": "4.0.6", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "@pie-lib/translator": "4.0.2", diff --git a/packages/categorize/configure/src/defaults.js b/packages/categorize/configure/src/defaults.js index 6fccb47ce4..1d3605df2c 100644 --- a/packages/categorize/configure/src/defaults.js +++ b/packages/categorize/configure/src/defaults.js @@ -1,10 +1,7 @@ -import { multiplePlacements } from './utils'; - export default { model: { allowAlternateEnabled: true, allowMaxChoicesPerCategory: false, - allowMultiplePlacementsEnabled: multiplePlacements.disabled, alternates: [], categories: [], categoriesPerRow: 2, diff --git a/packages/categorize/configure/src/design/index.jsx b/packages/categorize/configure/src/design/index.jsx index 89226d18e4..4d83692edd 100644 --- a/packages/categorize/configure/src/design/index.jsx +++ b/packages/categorize/configure/src/design/index.jsx @@ -538,10 +538,19 @@ export class Design extends React.Component { model.correctResponse || [], ); + const resolvedAllowMultiplePlacements = (() => { + if (model.allowMultiplePlacementsEnabled != null) { + return model.allowMultiplePlacementsEnabled; + } + const allChoiceIds = (model.correctResponse || []).flatMap((cr) => cr.choices || []); + const isExclusive = allChoiceIds.length === new Set(allChoiceIds).size; + return isExclusive ? multiplePlacements.disabled : multiplePlacements.enabled; + })(); + const choices = model.choices.map((c) => { c.correctResponseCount = this.countChoiceInCorrectResponse(c); // ensure categoryCount is set even though updatedModel hasn't been called - c.categoryCount = this.checkAllowMultiplePlacements(model.allowMultiplePlacementsEnabled, c); + c.categoryCount = this.checkAllowMultiplePlacements(resolvedAllowMultiplePlacements, c); return c; }); @@ -612,7 +621,7 @@ export class Design extends React.Component { hideSettings={settingsPanelDisabled} settings={ { }); }); + describe('allowMultiplePlacementsEnabled / categoryCount', () => { + const exclusiveCorrectResponse = [ + { category: '1', choices: ['1', '2'] }, + { category: '2', choices: ['3', '4'] }, + ]; + const nonExclusiveCorrectResponse = [ + { category: '1', choices: ['1', '2'] }, + { category: '2', choices: ['2', '3'] }, // '2' appears in both → reuse required + ]; + + it('explicit "Yes" → categoryCount 0 for all choices', async () => { + const q = makeQuestion({ allowMultiplePlacementsEnabled: 'Yes', correctResponse: exclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.choices.every((c) => c.categoryCount === 0)).toBe(true); + }); + + it('explicit "No" → categoryCount 1 for all choices', async () => { + const q = makeQuestion({ allowMultiplePlacementsEnabled: 'No', correctResponse: nonExclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.choices.every((c) => c.categoryCount === 1)).toBe(true); + }); + + it('no property + exclusive correct response → derived "No" → categoryCount 1', async () => { + const q = makeQuestion({ correctResponse: exclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.choices.every((c) => c.categoryCount === 1)).toBe(true); + }); + + it('no property + non-exclusive correct response → derived "Yes" → categoryCount 0', async () => { + const q = makeQuestion({ correctResponse: nonExclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.choices.every((c) => c.categoryCount === 0)).toBe(true); + }); + + it('no property + exclusive → sets allowMultiplePlacementsEnabled on out', async () => { + const q = makeQuestion({ correctResponse: exclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.allowMultiplePlacementsEnabled).toBe('No'); + }); + + it('no property + non-exclusive → sets allowMultiplePlacementsEnabled on out', async () => { + const q = makeQuestion({ correctResponse: nonExclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.allowMultiplePlacementsEnabled).toBe('Yes'); + }); + + it('explicit property → does not set allowMultiplePlacementsEnabled on out', async () => { + const q = makeQuestion({ allowMultiplePlacementsEnabled: 'No', correctResponse: exclusiveCorrectResponse }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.allowMultiplePlacementsEnabled).toBeUndefined(); + }); + + it('perChoice → uses categoryCount from each choice', async () => { + const q = makeQuestion({ + allowMultiplePlacementsEnabled: 'Set Per Choice', + choices: [ + { id: '1', content: 'Foo', categoryCount: 0 }, + { id: '2', content: 'Bar', categoryCount: 1 }, + ], + correctResponse: exclusiveCorrectResponse, + }); + const result = await model(q, {}, { mode: 'gather' }, jest.fn()); + expect(result.choices.find((c) => c.id === '1').categoryCount).toBe(0); + expect(result.choices.find((c) => c.id === '2').categoryCount).toBe(1); + }); + }); + describe('correct response', () => { it('returns correct response if env is correct', async () => { const sess = await createCorrectResponseSession(question, { diff --git a/packages/categorize/controller/src/defaults.js b/packages/categorize/controller/src/defaults.js index 852b9172d1..c6b4388762 100644 --- a/packages/categorize/controller/src/defaults.js +++ b/packages/categorize/controller/src/defaults.js @@ -1,7 +1,4 @@ -import { multiplePlacements } from './utils'; - export default { - allowMultiplePlacementsEnabled: multiplePlacements.disabled, alternates: [], categories: [], categoriesPerRow: 2, diff --git a/packages/categorize/controller/src/index.js b/packages/categorize/controller/src/index.js index 8cac3f3a07..87ff961add 100644 --- a/packages/categorize/controller/src/index.js +++ b/packages/categorize/controller/src/index.js @@ -154,13 +154,24 @@ export const model = async (question, session, env, updateSession) => { choices = await getShuffledChoices(choices, session, updateSession, 'id'); } + const resolvedAllowMultiple = (() => { + if (question.allowMultiplePlacementsEnabled != null) { + return question.allowMultiplePlacementsEnabled; + } + // Derive from correct response: if any choice id appears in more than one category, reuse is required + const allChoiceIds = (correctResponse || []).flatMap((cr) => cr.choices || []); + const isExclusive = allChoiceIds.length === new Set(allChoiceIds).size; + return isExclusive ? multiplePlacements.disabled : multiplePlacements.enabled; + })(); + choices = (choices || []).map((c) => { let categoryCount; - if (normalizedQuestion.allowMultiplePlacementsEnabled === multiplePlacements.enabled) { + if (resolvedAllowMultiple === multiplePlacements.enabled) { categoryCount = 0; - } else if (normalizedQuestion.allowMultiplePlacementsEnabled === multiplePlacements.disabled) { + } else if (resolvedAllowMultiple === multiplePlacements.disabled) { categoryCount = 1; } else { + // perChoice — use the value set on each choice individually categoryCount = c.categoryCount || 0; } return { ...c, categoryCount }; @@ -203,6 +214,7 @@ export const model = async (question, session, env, updateSession) => { possibleResponses, responseAreasToBeFilled, hasUnplacedChoices, + ...(question.allowMultiplePlacementsEnabled == null && { allowMultiplePlacementsEnabled: resolvedAllowMultiple }), }; if (role === 'instructor' && (mode === 'view' || mode === 'evaluate')) { @@ -276,7 +288,7 @@ export const getLogTrace = (model, session, env) => { } if (hasAlternates) { - traceLog.push(`Alternate response combinations are accepted for this question.`); + traceLog.push('Alternate response combinations are accepted for this question.'); } if (hasAlternates) { diff --git a/packages/categorize/package.json b/packages/categorize/package.json index 989eaa6eeb..8822976d92 100644 --- a/packages/categorize/package.json +++ b/packages/categorize/package.json @@ -16,7 +16,7 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/categorize": "2.0.2", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/correct-answer-toggle": "4.0.5", "@pie-lib/drag": "4.0.6", "@pie-lib/math-rendering": "5.1.0", diff --git a/packages/charting/configure/package.json b/packages/charting/configure/package.json index 8eefa6d0b8..6e333c8b38 100644 --- a/packages/charting/configure/package.json +++ b/packages/charting/configure/package.json @@ -12,9 +12,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/charting": "7.0.11", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/charting": "7.0.12", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", diff --git a/packages/charting/package.json b/packages/charting/package.json index a48ebea725..49105abdea 100644 --- a/packages/charting/package.json +++ b/packages/charting/package.json @@ -15,7 +15,7 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", - "@pie-lib/charting": "7.0.11", + "@pie-lib/charting": "7.0.12", "@pie-lib/correct-answer-toggle": "4.0.5", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", diff --git a/packages/complex-rubric/configure/package.json b/packages/complex-rubric/configure/package.json index 2e4e2a6f27..ff408897be 100644 --- a/packages/complex-rubric/configure/package.json +++ b/packages/complex-rubric/configure/package.json @@ -14,9 +14,9 @@ "@pie-element/multi-trait-rubric": "^8.1.0", "@pie-element/rubric": "^8.1.0", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/render-ui": "6.1.3", - "@pie-lib/rubric": "2.0.11", + "@pie-lib/rubric": "2.0.12", "debug": "^4.1.1", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", diff --git a/packages/complex-rubric/package.json b/packages/complex-rubric/package.json index 7a8cdf5208..d0cbee157d 100644 --- a/packages/complex-rubric/package.json +++ b/packages/complex-rubric/package.json @@ -13,7 +13,7 @@ "@pie-element/multi-trait-rubric": "^8.1.0", "@pie-element/rubric": "^8.1.0", "@pie-framework/pie-player-events": "^0.1.0", - "@pie-lib/rubric": "2.0.11", + "@pie-lib/rubric": "2.0.12", "classnames": "^2.2.6", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/drag-in-the-blank/configure/package.json b/packages/drag-in-the-blank/configure/package.json index 1b29ef07cb..d65e2d4eb4 100644 --- a/packages/drag-in-the-blank/configure/package.json +++ b/packages/drag-in-the-blank/configure/package.json @@ -12,9 +12,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/drag": "4.0.6", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-rendering": "5.1.0", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/drag-in-the-blank/package.json b/packages/drag-in-the-blank/package.json index 8cdb34745c..0a653e92e8 100644 --- a/packages/drag-in-the-blank/package.json +++ b/packages/drag-in-the-blank/package.json @@ -17,7 +17,7 @@ "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/correct-answer-toggle": "4.0.5", "@pie-lib/drag": "4.0.6", - "@pie-lib/mask-markup": "3.0.11", + "@pie-lib/mask-markup": "3.0.12", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "lodash-es": "^4.17.23", diff --git a/packages/drawing-response/configure/package.json b/packages/drawing-response/configure/package.json index 3160f2779f..7982a55ccc 100644 --- a/packages/drawing-response/configure/package.json +++ b/packages/drawing-response/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "debug": "^4.1.1", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", diff --git a/packages/ebsr/configure/package.json b/packages/ebsr/configure/package.json index e1a8cadc5d..7e4314ab4e 100644 --- a/packages/ebsr/configure/package.json +++ b/packages/ebsr/configure/package.json @@ -13,7 +13,7 @@ "@mui/material": "^7.3.4", "@pie-element/multiple-choice": "^13.2.0", "@pie-framework/pie-configure-events": "^1.2.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", "react": "18.3.1", diff --git a/packages/explicit-constructed-response/configure/package.json b/packages/explicit-constructed-response/configure/package.json index 867f8e3925..24583013bc 100644 --- a/packages/explicit-constructed-response/configure/package.json +++ b/packages/explicit-constructed-response/configure/package.json @@ -10,8 +10,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "classnames": "^2.2.6", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/explicit-constructed-response/package.json b/packages/explicit-constructed-response/package.json index 135e17a4fb..2d08189fc0 100644 --- a/packages/explicit-constructed-response/package.json +++ b/packages/explicit-constructed-response/package.json @@ -16,7 +16,7 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/correct-answer-toggle": "4.0.5", - "@pie-lib/mask-markup": "3.0.11", + "@pie-lib/mask-markup": "3.0.12", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "@pie-lib/translator": "4.0.2", diff --git a/packages/extended-text-entry/configure/package.json b/packages/extended-text-entry/configure/package.json index 5681b50eec..22707c5f8a 100644 --- a/packages/extended-text-entry/configure/package.json +++ b/packages/extended-text-entry/configure/package.json @@ -13,8 +13,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", "react": "18.3.1", diff --git a/packages/extended-text-entry/package.json b/packages/extended-text-entry/package.json index 6afa8a25ba..db6d1d2f1b 100644 --- a/packages/extended-text-entry/package.json +++ b/packages/extended-text-entry/package.json @@ -14,8 +14,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "classnames": "^2.2.6", diff --git a/packages/fraction-model/configure/package.json b/packages/fraction-model/configure/package.json index f0f73151b6..a2693f4fea 100644 --- a/packages/fraction-model/configure/package.json +++ b/packages/fraction-model/configure/package.json @@ -11,8 +11,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "debug": "^4.1.1", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", diff --git a/packages/fraction-model/package.json b/packages/fraction-model/package.json index 54ae126a49..8bd438b55f 100644 --- a/packages/fraction-model/package.json +++ b/packages/fraction-model/package.json @@ -12,7 +12,7 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/correct-answer-toggle": "4.0.5", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", diff --git a/packages/graphing-solution-set/configure/package.json b/packages/graphing-solution-set/configure/package.json index 9e0968cac0..14e03a89cb 100644 --- a/packages/graphing-solution-set/configure/package.json +++ b/packages/graphing-solution-set/configure/package.json @@ -12,9 +12,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", - "@pie-lib/graphing-solution-set": "4.0.11", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", + "@pie-lib/graphing-solution-set": "4.0.12", "@pie-lib/math-rendering": "5.1.0", "classnames": "^2.2.6", "debug": "^4.1.1", diff --git a/packages/graphing-solution-set/package.json b/packages/graphing-solution-set/package.json index 9410b25984..be4400443c 100644 --- a/packages/graphing-solution-set/package.json +++ b/packages/graphing-solution-set/package.json @@ -16,9 +16,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/correct-answer-toggle": "4.0.5", - "@pie-lib/graphing-solution-set": "4.0.11", + "@pie-lib/graphing-solution-set": "4.0.12", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "classnames": "^2.2.6", diff --git a/packages/graphing/configure/package.json b/packages/graphing/configure/package.json index 00c0bc1777..71fef0246d 100644 --- a/packages/graphing/configure/package.json +++ b/packages/graphing/configure/package.json @@ -12,9 +12,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", - "@pie-lib/graphing": "4.0.12", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", + "@pie-lib/graphing": "4.0.13", "@pie-lib/math-rendering": "5.1.0", "classnames": "^2.2.6", "debug": "^4.1.1", diff --git a/packages/graphing/controller/src/__tests__/index.test.js b/packages/graphing/controller/src/__tests__/index.test.js index ddc57e03a9..62388ff2f8 100644 --- a/packages/graphing/controller/src/__tests__/index.test.js +++ b/packages/graphing/controller/src/__tests__/index.test.js @@ -577,6 +577,142 @@ describe('outcome', () => { expect(result.score).toEqual(1); }); + // Regression for SCSTU-377: students were awarded full marks for exponential + // marks whose coordinates did not match the correct answer. The controller's + // equalExponential destructured non-existent keys, so every comparison returned + // true. These tests pin the full model + outcome scoring path. + it('Exponentials are correctly scored (SCSTU-377)', async () => { + const m = { + element: 'pie-element-graphing', + id: 'scstu-377', + graph: { width: 500, height: 500 }, + domain: { min: -20, max: 20, step: 1, labelStep: 5, axisLabel: 'x' }, + range: { min: -20, max: 60, step: 5, labelStep: 10, axisLabel: 'f(x)' }, + rationale: 'Rationale', + prompt: 'Prompt', + // actual assessment: correct exponential passes through (1,1) and (9,9) + answers: { + correctAnswer: { + marks: [{ type: 'exponential', root: { x: 1, y: 1 }, edge: { x: 9, y: 9 } }], + }, + }, + toolbarTools: ['exponential'], + }; + const env = { mode: 'evaluate' }; + + // student drew a different exponential (the ticket's (10,30),(18,50)) => incorrect + const wrongSession = { + answer: [{ type: 'exponential', root: { x: 10, y: 30 }, edge: { x: 18, y: 50 } }], + }; + expect((await outcome(await model(m, wrongSession, env), wrongSession, env)).score).toEqual(0); + + // any two positive points used to score full marks before the fix => still incorrect + const wrongSession2 = { + answer: [{ type: 'exponential', root: { x: 2, y: 3 }, edge: { x: 5, y: 4 } }], + }; + expect((await outcome(await model(m, wrongSession2, env), wrongSession2, env)).score).toEqual(0); + + // exact match => full marks + const correctSession = { + answer: [{ type: 'exponential', root: { x: 1, y: 1 }, edge: { x: 9, y: 9 } }], + }; + expect((await outcome(await model(m, correctSession, env), correctSession, env)).score).toEqual(1); + + // same curve sampled at a different point still matches => full marks + // y = 2 * 3^x passes through (0,2),(1,6) and (1,6),(2,18) + const curveModel = { + ...m, + answers: { + correctAnswer: { + marks: [{ type: 'exponential', root: { x: 0, y: 2 }, edge: { x: 1, y: 6 } }], + }, + }, + }; + const equivalentSession = { + answer: [{ type: 'exponential', root: { x: 1, y: 6 }, edge: { x: 2, y: 18 } }], + }; + expect((await outcome(await model(curveModel, equivalentSession, env), equivalentSession, env)).score).toEqual(1); + }); + + // Faithful reproduction of the SCSTU-377 ticket steps, using the exact + // coordinates from the SchoolCity assessment items. + describe('SchoolCity SCSTU-377 repro steps', () => { + // Item 1: an item with BOTH an Absolute and an Exponential response part. + // Correct answer: absolute (vertex (-5,-7), edge (-3,-5)) and + // exponential through (10,30),(14,50). + // Student: absolute matches, but exponential is (10,30),(18,50) (second + // coordinate 18,50 does not match 14,50). The item must NOT be full marks. + const item1Answers = { + correctAnswer: { + marks: [ + { type: 'absolute', root: { x: -5, y: -7 }, edge: { x: -3, y: -5 } }, + { type: 'exponential', root: { x: 10, y: 30 }, edge: { x: 14, y: 50 } }, + ], + }, + }; + const item1Session = { + answer: [ + { type: 'absolute', root: { x: -5, y: -7 }, edge: { x: -3, y: -5 } }, + { type: 'exponential', root: { x: 10, y: 30 }, edge: { x: 18, y: 50 } }, + ], + }; + + it('Item 1: absolute correct + wrong exponential is not full marks (partial => 0.5)', async () => { + const question = { answers: item1Answers, scoringType: 'partial scoring' }; + const env = { mode: 'evaluate', partialScoring: true }; + const result = await outcome(await model(question, item1Session, env), item1Session, env); + + // 1 of 2 correct (absolute), exponential incorrect + expect(result.score).toEqual(0.5); + }); + + it('Item 1: absolute correct + wrong exponential is not full marks (dichotomous => 0)', async () => { + const question = { answers: item1Answers, scoringType: 'all or nothing' }; + const env = { mode: 'evaluate', partialScoring: false }; + const result = await outcome(await model(question, item1Session, env), item1Session, env); + + expect(result.score).toEqual(0); + }); + + // Item 4: single Exponential response. Correct answer (1,1),(2,3). + // Student draws (1,1),(5,4) (second coordinate 5,4 does not match 2,3). + const item4Answers = { + correctAnswer: { + marks: [{ type: 'exponential', root: { x: 1, y: 1 }, edge: { x: 2, y: 3 } }], + }, + }; + const item4Session = { + answer: [{ type: 'exponential', root: { x: 1, y: 1 }, edge: { x: 5, y: 4 } }], + }; + + it('Item 4: wrong exponential scores 0 (partial)', async () => { + const question = { answers: item4Answers, scoringType: 'partial scoring' }; + const env = { mode: 'evaluate', partialScoring: true }; + const result = await outcome(await model(question, item4Session, env), item4Session, env); + + expect(result.score).toEqual(0); + }); + + it('Item 4: wrong exponential scores 0 (dichotomous)', async () => { + const question = { answers: item4Answers, scoringType: 'all or nothing' }; + const env = { mode: 'evaluate', partialScoring: false }; + const result = await outcome(await model(question, item4Session, env), item4Session, env); + + expect(result.score).toEqual(0); + }); + + it('Item 4: matching exponential scores full marks', async () => { + const question = { answers: item4Answers, scoringType: 'all or nothing' }; + const env = { mode: 'evaluate', partialScoring: false }; + const correctSession = { + answer: [{ type: 'exponential', root: { x: 1, y: 1 }, edge: { x: 2, y: 3 } }], + }; + const result = await outcome(await model(question, correctSession, env), correctSession, env); + + expect(result.score).toEqual(1); + }); + }); + it('Lines are correctly scored (ch4126)', async () => { const m = { id: '4028e4a24ca05186014cbae62f752be7', diff --git a/packages/graphing/controller/src/__tests__/utils.test.js b/packages/graphing/controller/src/__tests__/utils.test.js index 2950ac5399..851b9bd71e 100644 --- a/packages/graphing/controller/src/__tests__/utils.test.js +++ b/packages/graphing/controller/src/__tests__/utils.test.js @@ -9,6 +9,8 @@ import { equalCircle, equalSine, equalParabola, + equalAbsolute, + equalExponential, constructSegmentsFromPoints, removeDuplicateSegments, removeInvalidSegments, @@ -536,6 +538,70 @@ describe('equalParabola', () => { ); }); +describe('equalAbsolute', () => { + // y = a * |x - h| + k, where root = (h, k) is the vertex and edge is a point on a ray. + // Two absolutes are equal only when both the vertex AND the slope a match. + test.each([ + // identical vertex + edge => equal + [{root: p0_0, edge: p1_1}, {root: p0_0, edge: p1_1}, true], + // same vertex, edge mirrored across the axis of symmetry => same |slope| => equal + // (0,0),(1,2) and (0,0),(-1,2) both describe a = 2 + [{root: p0_0, edge: {x: 1, y: 2}}, {root: p0_0, edge: {x: -1, y: 2}}, true], + // same vertex, edge further along the same ray => same slope => equal + // (0,0),(1,2) and (0,0),(2,4) both describe a = 2 + [{root: p0_0, edge: {x: 1, y: 2}}, {root: p0_0, edge: {x: 2, y: 4}}, true], + // same vertex, different slope => not equal (a = 2 vs a = 3) + [{root: p0_0, edge: {x: 1, y: 2}}, {root: p0_0, edge: {x: 1, y: 3}}, false], + // different vertex => not equal even when slope matches + [{root: p0_0, edge: {x: 1, y: 2}}, {root: p1_1, edge: {x: 2, y: 3}}, false], + [{root: pNull, edge: pUndefined}, {root: pNull, edge: pUndefined}, true], + ])('%j, %j => %s', (a1, a2, expected) => { + const result = equalAbsolute(a1, a2); + + expect(result).toEqual(expected); + }); +}); + +describe('equalExponential', () => { + // y = a * b^x, derived from two points. Two exponentials are equal only when + // both a and b match. Regression coverage for SCSTU-377 / the PGM QA scoring bug + // where a destructuring typo made every comparison return true. + test.each([ + // identical points => equal + [{root: p0_1, edge: p1_1}, {root: p0_1, edge: p1_1}, true], + // same curve y = 2 * 3^x sampled at different points => equal + // (0,2),(1,6) and (1,6),(2,18) both describe a = 2, b = 3 + [{root: {x: 0, y: 2}, edge: {x: 1, y: 6}}, {root: {x: 1, y: 6}, edge: {x: 2, y: 18}}, true], + // same curve, comparison is order-independent + [{root: {x: 0, y: 2}, edge: {x: 2, y: 18}}, {root: {x: 0, y: 2}, edge: {x: 1, y: 6}}, true], + + // --- Jira reproduction cases (must NOT be equal) --- + // Actual SCSTU-377 assessment: correct answer is (1,1),(9,9); a student + // could draw any two positive points and still receive full marks. + [ + {root: {x: 10, y: 30}, edge: {x: 18, y: 50}}, + {root: {x: 1, y: 1}, edge: {x: 9, y: 9}}, + false, + ], + // Item 1: student drew (10,30),(18,50) but correct answer is (10,30),(14,50) + [ + {root: {x: 10, y: 30}, edge: {x: 18, y: 50}}, + {root: {x: 10, y: 30}, edge: {x: 14, y: 50}}, + false, + ], + // Item 4: student drew (1,1),(5,4) but correct answer is (1,1),(2,3) + [ + {root: {x: 1, y: 1}, edge: {x: 5, y: 4}}, + {root: {x: 1, y: 1}, edge: {x: 2, y: 3}}, + false, + ], + ])('%j, %j => %s', (e1, e2, expected) => { + const result = equalExponential(e1, e2); + + expect(result).toEqual(expected); + }); +}); + describe('constructSegmentsFromPoints', () => { test.each([ [ diff --git a/packages/graphing/controller/src/utils.js b/packages/graphing/controller/src/utils.js index 93a04452f8..6a5b6788e6 100644 --- a/packages/graphing/controller/src/utils.js +++ b/packages/graphing/controller/src/utils.js @@ -316,8 +316,8 @@ export const equalExponential = (p1, p2) => { const p1edge = edgeP1 || { ...rootP1 }; const p2edge = edgeP2 || { ...rootP2 }; - const { a1, b1 } = pointsToABForExponential(rootP1, p1edge); - const { a2, b2 } = pointsToABForExponential(rootP2, p2edge); + const { a: a1, b: b1 } = pointsToABForExponential(rootP1, p1edge); + const { a: a2, b: b2 } = pointsToABForExponential(rootP2, p2edge); // if both a and b value are equal return isEqual(a2, a1) && isEqual(b2, b1); diff --git a/packages/graphing/package.json b/packages/graphing/package.json index a2ba2c2034..46856b1aa5 100644 --- a/packages/graphing/package.json +++ b/packages/graphing/package.json @@ -16,7 +16,7 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/correct-answer-toggle": "4.0.5", - "@pie-lib/graphing": "4.0.12", + "@pie-lib/graphing": "4.0.13", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "classnames": "^2.2.6", diff --git a/packages/hotspot/configure/package.json b/packages/hotspot/configure/package.json index 66cfad3110..246b72034b 100644 --- a/packages/hotspot/configure/package.json +++ b/packages/hotspot/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "debug": "^4.1.1", "konva": "8.3.0", "lodash-es": "^4.17.23", diff --git a/packages/hotspot/src/hotspot/__tests__/index.test.jsx b/packages/hotspot/src/hotspot/__tests__/index.test.jsx new file mode 100644 index 0000000000..3796f51088 --- /dev/null +++ b/packages/hotspot/src/hotspot/__tests__/index.test.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { renderMath } from '@pie-lib/math-rendering'; +import HotspotComponent from '../index'; + +jest.mock('@pie-lib/math-rendering', () => ({ + renderMath: jest.fn(), +})); + +jest.mock('../container', () => (props) =>
); + +describe('HotspotComponent', () => { + const defaultModel = { + disabled: false, + imageUrl: 'http://example.com/image.png', + prompt: 'prompt', + mode: 'gather', + multipleCorrect: false, + shapes: { rectangles: [], polygons: [], circles: [] }, + dimensions: { width: 800, height: 600 }, + strokeWidth: 5, + }; + + const getWrapper = (props) => + render( + , + ); + + beforeEach(() => { + renderMath.mockClear(); + }); + + describe('math rendering', () => { + it('calls renderMath on mount', () => { + getWrapper(); + expect(renderMath).toHaveBeenCalled(); + }); + + it('passes the rendered dom node to renderMath on mount', () => { + getWrapper(); + expect(renderMath).toHaveBeenCalledWith(expect.any(HTMLElement)); + }); + + it('calls renderMath again on update', () => { + const { rerender } = getWrapper(); + renderMath.mockClear(); + + rerender( + , + ); + + expect(renderMath).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/hotspot/src/hotspot/index.jsx b/packages/hotspot/src/hotspot/index.jsx index 4422d5e910..eb0aa8e336 100644 --- a/packages/hotspot/src/hotspot/index.jsx +++ b/packages/hotspot/src/hotspot/index.jsx @@ -1,7 +1,9 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import CorrectAnswerToggle from '@pie-lib/correct-answer-toggle'; import { color, Collapsible, hasText, PreviewPrompt, UiLayout, hasMedia } from '@pie-lib/render-ui'; +import { renderMath } from '@pie-lib/math-rendering'; import { styled } from '@mui/material/styles'; import Container from './container'; @@ -54,6 +56,18 @@ class HotspotComponent extends React.Component { if (target) { this.observer.observe(target, { attributes: true, attributeFilter: ['style'] }); } + + // eslint-disable-next-line react/no-find-dom-node + const domNode = ReactDOM.findDOMNode(this); + + renderMath(domNode); + } + + componentDidUpdate() { + // eslint-disable-next-line react/no-find-dom-node + const domNode = ReactDOM.findDOMNode(this); + + renderMath(domNode); } componentWillUnmount() { diff --git a/packages/image-cloze-association/configure/package.json b/packages/image-cloze-association/configure/package.json index 15d4c37b13..11531f14d0 100644 --- a/packages/image-cloze-association/configure/package.json +++ b/packages/image-cloze-association/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "debug": "^4.1.1", "prop-types": "^15.8.1", "react": "18.3.1", diff --git a/packages/inline-dropdown/configure/package.json b/packages/inline-dropdown/configure/package.json index c8abbbd3fe..82eafbad65 100644 --- a/packages/inline-dropdown/configure/package.json +++ b/packages/inline-dropdown/configure/package.json @@ -10,8 +10,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "classnames": "^2.2.6", diff --git a/packages/inline-dropdown/package.json b/packages/inline-dropdown/package.json index 13ed80c181..14e678dd0b 100644 --- a/packages/inline-dropdown/package.json +++ b/packages/inline-dropdown/package.json @@ -16,7 +16,7 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/correct-answer-toggle": "4.0.5", - "@pie-lib/mask-markup": "3.0.11", + "@pie-lib/mask-markup": "3.0.12", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", "lodash-es": "^4.17.23", diff --git a/packages/likert/configure/package.json b/packages/likert/configure/package.json index 7ac3fb3b27..09986f67ef 100644 --- a/packages/likert/configure/package.json +++ b/packages/likert/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "prop-types": "^15.8.1", diff --git a/packages/match/configure/package.json b/packages/match/configure/package.json index 777722187d..123be9ca3f 100644 --- a/packages/match/configure/package.json +++ b/packages/match/configure/package.json @@ -13,9 +13,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/drag": "4.0.6", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "@pie-lib/test-utils": "2.0.2", "classnames": "^2.2.6", diff --git a/packages/math-inline/configure/package.json b/packages/math-inline/configure/package.json index 1c2d2d8aed..dcec365954 100644 --- a/packages/math-inline/configure/package.json +++ b/packages/math-inline/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-input": "8.1.1", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/math-toolbar": "3.0.6", diff --git a/packages/math-templated/configure/package.json b/packages/math-templated/configure/package.json index 9ee547f816..c640ba2ae9 100644 --- a/packages/math-templated/configure/package.json +++ b/packages/math-templated/configure/package.json @@ -11,8 +11,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/math-toolbar": "3.0.6", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", diff --git a/packages/math-templated/package.json b/packages/math-templated/package.json index 850362f78d..f755585b43 100644 --- a/packages/math-templated/package.json +++ b/packages/math-templated/package.json @@ -13,7 +13,7 @@ "@mui/material": "^7.3.4", "@pie-framework/pie-player-events": "^0.1.0", "@pie-lib/correct-answer-toggle": "4.0.5", - "@pie-lib/mask-markup": "3.0.11", + "@pie-lib/mask-markup": "3.0.12", "@pie-lib/math-input": "8.1.1", "@pie-lib/math-rendering": "5.1.0", "@pie-lib/render-ui": "6.1.3", diff --git a/packages/matrix/configure/package.json b/packages/matrix/configure/package.json index e0cffa753d..25b19220c6 100644 --- a/packages/matrix/configure/package.json +++ b/packages/matrix/configure/package.json @@ -12,8 +12,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "prop-types": "^15.8.1", diff --git a/packages/multi-trait-rubric/configure/package.json b/packages/multi-trait-rubric/configure/package.json index 95c34e1335..d99cfb3693 100644 --- a/packages/multi-trait-rubric/configure/package.json +++ b/packages/multi-trait-rubric/configure/package.json @@ -11,9 +11,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/drag": "4.0.6", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/multiple-choice/configure/package.json b/packages/multiple-choice/configure/package.json index ac022f4e20..d2f9ccefcb 100644 --- a/packages/multiple-choice/configure/package.json +++ b/packages/multiple-choice/configure/package.json @@ -10,8 +10,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/number-line/configure/package.json b/packages/number-line/configure/package.json index 1f773c32e3..16c3413b7b 100644 --- a/packages/number-line/configure/package.json +++ b/packages/number-line/configure/package.json @@ -10,8 +10,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "lodash-es": "^4.17.23", "react": "18.3.1", diff --git a/packages/passage/configure/package.json b/packages/passage/configure/package.json index 2f43144bcb..49c3b6ff58 100644 --- a/packages/passage/configure/package.json +++ b/packages/passage/configure/package.json @@ -13,8 +13,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "lodash-es": "^4.17.23", "prop-types": "^15.8.1", "react": "18.3.1", diff --git a/packages/passage/src/stimulus-tabs.jsx b/packages/passage/src/stimulus-tabs.jsx index 424e960cf8..eab59fe6a9 100644 --- a/packages/passage/src/stimulus-tabs.jsx +++ b/packages/passage/src/stimulus-tabs.jsx @@ -371,7 +371,7 @@ class StimulusTabs extends React.Component { top: 0, zIndex: 1, }, - background: color.background(), + background: 'var(--pie-passage-header-background, rgba(255,255,255,0))', color: color.text(), fontFamily: 'Roboto, sans-serif', // Freeze the tabs' physical size at their 200%-zoom appearance @@ -381,7 +381,7 @@ class StimulusTabs extends React.Component { // so the reclaimed space flows to the passage content below. zoom: zoomCompensation, '& .MuiTabs-list': { - backgroundColor: color.white(), + backgroundColor: 'var(--pie-passage-header-background, #ffffff)', borderBottom: '1px solid #D9DADA', }, '& .MuiTabs-indicator': { diff --git a/packages/placement-ordering/configure/package.json b/packages/placement-ordering/configure/package.json index 080d995830..ef6a59cc02 100644 --- a/packages/placement-ordering/configure/package.json +++ b/packages/placement-ordering/configure/package.json @@ -11,9 +11,9 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", + "@pie-lib/config-ui": "13.0.12", "@pie-lib/drag": "4.0.6", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/rubric/configure/package.json b/packages/rubric/configure/package.json index 63cfbeef80..a9e87027a0 100644 --- a/packages/rubric/configure/package.json +++ b/packages/rubric/configure/package.json @@ -11,10 +11,10 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/render-ui": "6.1.3", - "@pie-lib/rubric": "2.0.11", + "@pie-lib/rubric": "2.0.12", "debug": "^4.1.1", "react": "18.3.1", "react-dom": "18.3.1" diff --git a/packages/ruler/configure/package.json b/packages/ruler/configure/package.json index b826d21f7e..b78f96038d 100644 --- a/packages/ruler/configure/package.json +++ b/packages/ruler/configure/package.json @@ -10,7 +10,7 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11" + "@pie-lib/config-ui": "13.0.12" }, "author": "", "license": "ISC" diff --git a/packages/select-text/configure/package.json b/packages/select-text/configure/package.json index 091074e6c1..a4941bb1e9 100644 --- a/packages/select-text/configure/package.json +++ b/packages/select-text/configure/package.json @@ -11,8 +11,8 @@ "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", "@pie-framework/pie-configure-events": "^1.3.0", - "@pie-lib/config-ui": "13.0.11", - "@pie-lib/editable-html-tip-tap": "2.1.9", + "@pie-lib/config-ui": "13.0.12", + "@pie-lib/editable-html-tip-tap": "2.1.10", "@pie-lib/text-select": "3.0.5", "debug": "^4.1.1", "lodash-es": "^4.17.23", diff --git a/packages/select-text/src/__tests__/main.test.jsx b/packages/select-text/src/__tests__/main.test.jsx index f7ac924391..377395bff0 100644 --- a/packages/select-text/src/__tests__/main.test.jsx +++ b/packages/select-text/src/__tests__/main.test.jsx @@ -1,5 +1,6 @@ import { render } from '@testing-library/react'; import React from 'react'; +import { renderMath } from '@pie-lib/math-rendering'; import { Main } from '../main'; jest.mock('@pie-lib/text-select', () => ({ @@ -8,6 +9,10 @@ jest.mock('@pie-lib/text-select', () => ({ Legend: (props) =>
, })); +jest.mock('@pie-lib/math-rendering', () => ({ + renderMath: jest.fn(), +})); + describe('main', () => { const getWrapper = (props) => { return render( @@ -48,6 +53,43 @@ describe('main', () => { return instance; }; + beforeEach(() => { + renderMath.mockClear(); + }); + + describe('math rendering', () => { + it('calls renderMath on mount', () => { + getWrapper(); + expect(renderMath).toHaveBeenCalled(); + }); + + it('passes the rendered dom node to renderMath on mount', () => { + getWrapper(); + expect(renderMath).toHaveBeenCalledWith(expect.any(HTMLElement)); + }); + + it('calls renderMath again on update', () => { + const { rerender } = getWrapper(); + renderMath.mockClear(); + + rerender( +
, + ); + + expect(renderMath).toHaveBeenCalled(); + }); + }); + describe('logic', () => { it('shows correct answer', () => { const correctTokens = [{ start: 0, end: 1, text: 'f', correct: true, oldStart: 0, oldEnd: 1 }]; diff --git a/packages/select-text/src/main.jsx b/packages/select-text/src/main.jsx index f64a170fca..5bc9e8171c 100644 --- a/packages/select-text/src/main.jsx +++ b/packages/select-text/src/main.jsx @@ -1,8 +1,10 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { TextSelect, Legend } from '@pie-lib/text-select'; import CorrectAnswerToggle from '@pie-lib/correct-answer-toggle'; import { color, Feedback, Collapsible, hasText, hasMedia, PreviewPrompt, UiLayout } from '@pie-lib/render-ui'; +import { renderMath } from '@pie-lib/math-rendering'; import { styled } from '@mui/material/styles'; import generateModel from './utils'; @@ -57,6 +59,20 @@ export class Main extends React.Component { }); } + componentDidMount() { + // eslint-disable-next-line react/no-find-dom-node + const domNode = ReactDOM.findDOMNode(this); + + renderMath(domNode); + } + + componentDidUpdate() { + // eslint-disable-next-line react/no-find-dom-node + const domNode = ReactDOM.findDOMNode(this); + + renderMath(domNode); + } + toggleShowCorrect = () => { this.setState({ showCorrectAnswer: !this.state.showCorrectAnswer }); }; diff --git a/pslb/pslb.config.js b/pslb/pslb.config.js index 78109bd343..8e609707cb 100644 --- a/pslb/pslb.config.js +++ b/pslb/pslb.config.js @@ -44,12 +44,12 @@ module.exports = { libs: { repository: 'pie-framework/pie-elements', packages: [ - { name: '@pie-lib/drag-module', version: '4.0.15' }, - { name: '@pie-lib/math-rendering-module', version: '5.1.0' }, - { name: '@pie-lib/math-edit-module', version: '4.2.10' }, - { name: '@pie-lib/shared-module', version: '5.2.10' }, - { name: '@pie-lib/editable-html-module', version: '7.1.12' }, - { name: '@pie-lib/config-module', version: '4.0.15' }, + { name: '@pie-lib/drag-module', version: '4.0.16' }, + { name: '@pie-lib/math-rendering-module', version: '5.1.1' }, + { name: '@pie-lib/math-edit-module', version: '4.2.11' }, + { name: '@pie-lib/shared-module', version: '5.2.11' }, + { name: '@pie-lib/editable-html-module', version: '7.1.13' }, + { name: '@pie-lib/config-module', version: '4.0.16' }, ], }, }; diff --git a/yarn.lock b/yarn.lock index f1b7d45782..c9bfbfb4da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2833,10 +2833,10 @@ debug "^4.1.1" lodash-es "^4.17.23" -"@pie-lib/charting@7.0.11": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/charting/-/charting-7.0.11.tgz#8d6a59dd8791b4c56a9720c39ec505ce6fc6d7ed" - integrity sha512-5pIasKytnLivWNXOW8OfmBjyXRBwF3H16a12x14gJRWAkaSXLqZ5QSSo9xAjX4d9VMEFQG9dBcYBs3tXaneScA== +"@pie-lib/charting@7.0.12": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/charting/-/charting-7.0.12.tgz#1dff3c50349b813e9b0b3dc9f8f7b4b6123e2491" + integrity sha512-2O6pEJBeqUZHqESYKBcL6SUbFVC6uVpXWiDW3MP6j/mF8v5CSlgHuE0UBsdlQpx6YO/Nxtom3d+tCWmP2+Ctrw== dependencies: "@emotion/react" "^11.14.0" "@emotion/styled" "^11.14.1" @@ -2844,7 +2844,7 @@ "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" "@pie-lib/math-rendering" "^5.1.0" - "@pie-lib/plot" "^4.0.11" + "@pie-lib/plot" "^4.0.12" "@pie-lib/render-ui" "^6.1.3" "@pie-lib/translator" "^4.0.2" "@visx/axis" "^3.0.0" @@ -2864,16 +2864,16 @@ react-draggable "^3.3.0" react-input-autosize "^2.2.1" -"@pie-lib/config-ui@13.0.11": - version "13.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/config-ui/-/config-ui-13.0.11.tgz#ff7b8d427f167958ae8ef49c7dd463151a03d50f" - integrity sha512-TVCRSQOgPNws9XDSbC4DqV/EbbRXy9gPklOnRwuiiX2IAJDCNg4AKpMUjFAm4AbUz1rhxkmr6R4R7MzKl4PpiQ== +"@pie-lib/config-ui@13.0.12": + version "13.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/config-ui/-/config-ui-13.0.12.tgz#659eccaca6a378eb0c781a07255c25c7b3c6bcb5" + integrity sha512-+b6fqVQqIc8AF18zamEamn9vZMxZOSIawcP+/3rhfBvSLkShpMUO0lWjHyVg7uU1V7+yj9uE1hWSSqRUaYZx0w== dependencies: "@emotion/react" "^11.14.0" "@emotion/styled" "^11.14.1" "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" "@pie-lib/icons" "^4.0.3" "@pie-lib/render-ui" "^6.1.3" assert "^1.4.1" @@ -2926,10 +2926,10 @@ prop-types "^15.7.2" react "^18.2.0" -"@pie-lib/editable-html-tip-tap@2.1.9", "@pie-lib/editable-html-tip-tap@^2.1.9": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@pie-lib/editable-html-tip-tap/-/editable-html-tip-tap-2.1.9.tgz#b017f2aeba99175c45de40d6acf0033501d121c3" - integrity sha512-0VoZpMa/V4ccXmy+PhiT6Yjgm23LJQnqal9YeH0EvnnmpTiiruSyJHa3wh96QyP1kCP0bo0LpILVOGSL2rjY2A== +"@pie-lib/editable-html-tip-tap@2.1.10", "@pie-lib/editable-html-tip-tap@^2.1.10": + version "2.1.10" + resolved "https://registry.yarnpkg.com/@pie-lib/editable-html-tip-tap/-/editable-html-tip-tap-2.1.10.tgz#08a302c0737adcfa284a187dead2149801b6aacc" + integrity sha512-cWQ2at4DR0v8Q/nEV5F8mDa9yCeZjXrYqvHjrVAJeEk51orW/sqFd5toOsikitnC2MEcWI0aFcnh4lV+ks0pTg== dependencies: "@dnd-kit/core" "6.3.1" "@dnd-kit/modifiers" "9.0.0" @@ -3013,10 +3013,10 @@ resolved "https://registry.yarnpkg.com/@pie-lib/feedback/-/feedback-2.0.2.tgz#897e249ba78f8203aa39d75bd46b59e499b8e773" integrity sha512-y7NKifptZJJUb5eynFz+ppai4XoeOjlowSh6BrIrvghEshGkoX+O4EYzQCXcSlFuyOjjiuYaHd/OzNuifaYG5Q== -"@pie-lib/graphing-solution-set@4.0.11": - version "4.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/graphing-solution-set/-/graphing-solution-set-4.0.11.tgz#897f90cfccab5bda082e23bc6fe6672646205278" - integrity sha512-R2Nvx6PBRlReePwoXiwTcpddCy5HxPYSfelPh7EzDMeslFd/PI0IiQaCK4caxDKkj7ERM4RX+sbYY+tr1rL3Pw== +"@pie-lib/graphing-solution-set@4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/graphing-solution-set/-/graphing-solution-set-4.0.12.tgz#dee134ebbd63487e5ef6b4cbc4556ba0361ebd2a" + integrity sha512-Wqxd0br22la6C0o1RhqJlhaaFE62CM+3A3VdEfM926RCuYpNxigdNRxLvrv0MtFyDqmAbGVhOhpPAPL/fKA1uw== dependencies: "@dnd-kit/sortable" "10.0.0" "@emotion/react" "^11.14.0" @@ -3025,9 +3025,9 @@ "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" "@pie-lib/drag" "^4.0.6" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" "@pie-lib/graphing-utils" "^3.0.2" - "@pie-lib/plot" "^4.0.11" + "@pie-lib/plot" "^4.0.12" "@pie-lib/render-ui" "^6.1.3" "@pie-lib/tools" "^2.0.3" "@pie-lib/translator" "^4.0.2" @@ -3063,10 +3063,10 @@ debug "^4.1.1" lodash-es "^4.17.23" -"@pie-lib/graphing@4.0.12": - version "4.0.12" - resolved "https://registry.yarnpkg.com/@pie-lib/graphing/-/graphing-4.0.12.tgz#797b11d586fd21b2cc1ca203afd63542f7986c58" - integrity sha512-4/vJ998wnp7CunflZSq/s/o5EHliXLZ6cEysyFiocLKmfZWr0JZzevuc6qhhnjtzJ3aSa/3gvdoTNGJdq8DTCA== +"@pie-lib/graphing@4.0.13": + version "4.0.13" + resolved "https://registry.yarnpkg.com/@pie-lib/graphing/-/graphing-4.0.13.tgz#08c1fa792fcbac02de2890bca6f5e63b20e90c33" + integrity sha512-I20TDIObou3mEddLiADxnO5lKm/PEF0j0LqH/sUO03a4umw6LM15Ffpq6KjW8gvWPul9l81maG7zXBhzdZjaMQ== dependencies: "@dnd-kit/sortable" "10.0.0" "@emotion/react" "^11.14.0" @@ -3075,9 +3075,9 @@ "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" "@pie-lib/drag" "^4.0.6" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" "@pie-lib/graphing-utils" "^3.0.2" - "@pie-lib/plot" "^4.0.11" + "@pie-lib/plot" "^4.0.12" "@pie-lib/render-ui" "^6.1.3" "@pie-lib/translator" "^4.0.2" "@visx/axis" "^3.0.0" @@ -3114,17 +3114,17 @@ "@mui/material" "^7.3.4" prop-types "^15.6.2" -"@pie-lib/mask-markup@3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/mask-markup/-/mask-markup-3.0.11.tgz#228b2f913f5cd68bd7193d148c75f94b4bbb8149" - integrity sha512-2efjbzfoAYTn4ehk8NzvtU34J2ynOcAnd5CPaM3YdGVtxzE/9bxKhL3FPGWiX4I+HF75iEne0keinmPjZ2ZiyQ== +"@pie-lib/mask-markup@3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/mask-markup/-/mask-markup-3.0.12.tgz#048c1000e591e447095e8213b8d72a7c1847d2b1" + integrity sha512-8NNYQd09fVKGjr3emso70LjHA1VFL3N60GQFXhrKemoM05Q6zM/NojYPyr8imlnJktosqck5/YYds42g5coFxQ== dependencies: "@emotion/react" "^11.14.0" "@emotion/styled" "^11.14.1" "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" "@pie-lib/drag" "^4.0.6" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" "@pie-lib/math-rendering" "^5.1.0" "@pie-lib/render-ui" "^6.1.3" classnames "^2.2.6" @@ -3198,17 +3198,17 @@ lodash-es "^4.17.23" prop-types "^15.7.2" -"@pie-lib/plot@4.0.11", "@pie-lib/plot@^4.0.11": - version "4.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/plot/-/plot-4.0.11.tgz#89b34d61caff4230e8541a6e0382864d1745b49e" - integrity sha512-6CDHUps5S2mqqIyOPP5HxzybDEmiTyP+rW4YTkKND7o+EUAScAJigkWiVps0kIqWvceZJu5MQWrVHtt4AwsFBw== +"@pie-lib/plot@4.0.12", "@pie-lib/plot@^4.0.12": + version "4.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/plot/-/plot-4.0.12.tgz#a4c5ae6be71420b349e656a52c2aa3354ec620b6" + integrity sha512-SKKhGyQG67HGp3Z/BaeSPIaLXfg5gtin4Nlj/FIjEqI4gtIG2jVacEB07O2Ns/GctxOeuom9sxzhOmuBqQ++tA== dependencies: "@emotion/react" "^11.14.0" "@emotion/styled" "^11.14.1" "@mapbox/point-geometry" "^1.1.0" "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" "@pie-lib/render-ui" "^6.1.3" assert "^1.4.1" d3-scale "^4.0.2" @@ -3239,17 +3239,17 @@ prop-types "^15.7.2" react-transition-group "^4.4.5" -"@pie-lib/rubric@2.0.11": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@pie-lib/rubric/-/rubric-2.0.11.tgz#9bb37fdaace0fc2405bc2283b1a2c639c4207961" - integrity sha512-QR2BUvrwE9Lk1MBXobf17bD+sJ+kAyw9JS+FG985HO0gnyNmQlpZLX8FZHouORc3nCOPjazJwLdeM6XVq56Tig== +"@pie-lib/rubric@2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@pie-lib/rubric/-/rubric-2.0.12.tgz#d9a49015268322ad12f2e503f274094a0a83d8e3" + integrity sha512-JbMQeJ2e7FJ4dHVA9Fsv693bSLIHVCw0El5BbvffPmKUAA0N0eZxd8dUxtC617rlTUhgbtdAq16b95XMYUK7Mg== dependencies: "@emotion/react" "^11.14.0" "@emotion/styled" "^11.14.1" "@hello-pangea/dnd" "^18.0.1" "@mui/icons-material" "^7.3.4" "@mui/material" "^7.3.4" - "@pie-lib/editable-html-tip-tap" "^2.1.9" + "@pie-lib/editable-html-tip-tap" "^2.1.10" debug "^4.1.1" lodash-es "^4.17.23" prop-types "^15.7.2"