From 5f9878de35abef68f1fd03bcc053e1c2b52c67eb Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Fri, 25 Oct 2024 08:46:22 -0500 Subject: [PATCH 01/13] convert mathList and numberList components to composites --- packages/doenetml-worker/src/Core.js | 7 +- packages/doenetml-worker/src/CoreWorker.js | 1 + .../doenetml-worker/src/components/Aliases.js | 2 - .../doenetml-worker/src/components/Answer.js | 2 +- .../src/components/Function.js | 2 +- .../doenetml-worker/src/components/Math.js | 32 +- .../src/components/MathList.js | 1005 +++++------------ .../src/components/MathOperators.js | 3 +- .../doenetml-worker/src/components/Number.js | 5 +- .../src/components/NumberList.js | 689 ++++------- .../src/components/Substitute.js | 2 +- .../src/components/TupleList.js | 1 - .../components/abstract/MathBaseOperator.js | 284 +---- .../src/test/tagSpecific/answer.test.ts | 31 +- .../src/test/tagSpecific/copy.test.ts | 2 +- .../test/tagSpecific/mathoperators.test.ts | 2 +- .../src/test/tagSpecific/polygon.test.ts | 30 +- .../src/test/tagSpecific/polyline.test.ts | 18 +- .../selectsamplerandomnumbers.test.ts | 6 +- .../doenetml-worker/src/utils/rounding.js | 63 +- .../src/Viewer/renderers/mathList.jsx | 39 - .../src/Viewer/renderers/numberList.jsx | 35 - 22 files changed, 664 insertions(+), 1597 deletions(-) delete mode 100644 packages/doenetml/src/Viewer/renderers/mathList.jsx delete mode 100644 packages/doenetml/src/Viewer/renderers/numberList.jsx diff --git a/packages/doenetml-worker/src/Core.js b/packages/doenetml-worker/src/Core.js index e452b1d11..305f5998f 100644 --- a/packages/doenetml-worker/src/Core.js +++ b/packages/doenetml-worker/src/Core.js @@ -2584,7 +2584,8 @@ export default class Core { } if ( - component.shadows + component.shadows && + !component.shadows.propVariable //&& // this.componentInfoObjects.isCompositeComponent({ // componentType: component.componentType, @@ -3201,6 +3202,7 @@ export default class Core { }); component.replacements = replacementResult.components; } catch (e) { + // throw e; component.replacements = await this.setErrorReplacements({ composite: component, message: e.message, @@ -4118,6 +4120,7 @@ export default class Core { } let stateDef = stateVariableDefinitions[primaryStateVariableForDefinition]; + stateDef.isShadow = true; stateDef.returnDependencies = () => ({ adapterTargetVariable: { dependencyType: "stateVariable", @@ -9598,6 +9601,7 @@ export default class Core { newComponents = createResult.components; } catch (e) { + // throw e; newComponents = await this.setErrorReplacements({ composite: component, message: e.message, @@ -10320,6 +10324,7 @@ export default class Core { }); newComponents = createResult.components; } catch (e) { + // throw e; newComponents = await this.setErrorReplacements({ composite: shadowingComponent, message: e.message, diff --git a/packages/doenetml-worker/src/CoreWorker.js b/packages/doenetml-worker/src/CoreWorker.js index 1b02bbfc7..f63ba598f 100644 --- a/packages/doenetml-worker/src/CoreWorker.js +++ b/packages/doenetml-worker/src/CoreWorker.js @@ -183,6 +183,7 @@ async function createCore(args) { } queuedRequestActions = []; } catch (e) { + // throw e; postMessage({ messageType: "inErrorState", coreId: coreArgs.coreId, diff --git a/packages/doenetml-worker/src/components/Aliases.js b/packages/doenetml-worker/src/components/Aliases.js index 7ffdbcd28..dab51d86d 100644 --- a/packages/doenetml-worker/src/components/Aliases.js +++ b/packages/doenetml-worker/src/components/Aliases.js @@ -36,13 +36,11 @@ export class Ylabel extends Label { export class MatrixRow extends MathList { static componentType = "matrixRow"; - static rendererType = "mathList"; static excludeFromSchema = true; } export class MatrixColumn extends MathList { static componentType = "matrixColumn"; - static rendererType = "mathList"; static excludeFromSchema = true; } diff --git a/packages/doenetml-worker/src/components/Answer.js b/packages/doenetml-worker/src/components/Answer.js index fef19f330..c2b744257 100644 --- a/packages/doenetml-worker/src/components/Answer.js +++ b/packages/doenetml-worker/src/components/Answer.js @@ -355,7 +355,7 @@ export default class Answer extends InlineComponent { } else if ( componentIsSpecifiedType(grandChild, "when") ) { - // have to test for when before boolean, sincd when is derived from boolean! + // have to test for `when` before `boolean`, since `when` is derived from `boolean`! } else if ( componentIsSpecifiedType(grandChild, "math") || componentIsSpecifiedType( diff --git a/packages/doenetml-worker/src/components/Function.js b/packages/doenetml-worker/src/components/Function.js index 838bb6eda..68ec75dca 100644 --- a/packages/doenetml-worker/src/components/Function.js +++ b/packages/doenetml-worker/src/components/Function.js @@ -258,7 +258,7 @@ export default class Function extends InlineComponent { }; let roundingDefinitions = returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch: ["functions"], + childGroupsIfSingleMatch: ["functions"], }); Object.assign(stateVariableDefinitions, roundingDefinitions); diff --git a/packages/doenetml-worker/src/components/Math.js b/packages/doenetml-worker/src/components/Math.js index 47a6ae908..2ce8a8a91 100644 --- a/packages/doenetml-worker/src/components/Math.js +++ b/packages/doenetml-worker/src/components/Math.js @@ -198,9 +198,8 @@ export default class MathComponent extends InlineComponent { Object.assign(stateVariableDefinitions, anchorDefinition); let roundingDefinitions = returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch: ["maths"], + childGroupsIfSingleMatch: ["maths"], childGroupsToStopSingleMatch: ["strings"], - includeListParents: true, }); Object.assign(stateVariableDefinitions, roundingDefinitions); @@ -710,33 +709,12 @@ export default class MathComponent extends InlineComponent { dependencyType: "stateVariable", variableName: "expand", }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "mathList", - variableName: "numComponentsToDisplayByChild", - }, }), - definition: function ({ dependencyValues, componentName }) { + definition: function ({ dependencyValues }) { let value = dependencyValues.value; - if ( - dependencyValues.parentNComponentsToDisplayByChild?.[ - componentName - ] > 0 - ) { - // math is a list inside a mathList that is displaying only a fraction of the list - value = me.fromAst( - value.tree.slice( - 0, - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ] + 1, - ), - ); - } - // for display via latex and text, round any decimal numbers to the significant digits - // determined by displaydigits, displaydecimals, and/or displaySmallAsZero + // determined by displayDigits, displayDecimals, and/or displaySmallAsZero let rounded = roundForDisplay({ value, dependencyValues, @@ -1894,7 +1872,7 @@ function calculateExpressionWithCodes({ dependencyValues, changes }) { // concatenate strings and with a numbered code for each math child // (that will be parsed to form expression with codes) // If compositeReplacementAsList is true, -// then add commas betweeen the components that are all from one composite, +// then add commas between the components that are all from one composite, // if that composite has asList set to true. // Put parens around that list in some cases, as described below. function createInputStringFromChildren({ @@ -2101,7 +2079,7 @@ function createInputStringFromChildrenSub({ newChildren.push(listString); } else { // We are not turning the children in a list, - // so just concatentate the strings + // so just concatenate the strings newChildren.push(childrenInRange.join("")); } diff --git a/packages/doenetml-worker/src/components/MathList.js b/packages/doenetml-worker/src/components/MathList.js index f81410747..b4c055abc 100644 --- a/packages/doenetml-worker/src/components/MathList.js +++ b/packages/doenetml-worker/src/components/MathList.js @@ -1,17 +1,19 @@ -import InlineComponent from "./abstract/InlineComponent"; +import CompositeComponent from "./abstract/CompositeComponent"; import me from "math-expressions"; import { returnGroupIntoComponentTypeSeparatedBySpacesOutsideParens } from "./commonsugar/lists"; import { convertValueToMathExpression } from "@doenet/utils"; +import { returnRoundingAttributes } from "../utils/rounding"; import { - returnRoundingAttributeComponentShadowing, - returnRoundingAttributes, - returnRoundingStateVariableDefinitions, -} from "../utils/rounding"; -import { roundForDisplay } from "../utils/math"; + convertAttributesForComponentType, + postProcessCopy, +} from "../utils/copy"; +import { processAssignNames } from "../utils/naming"; -export default class MathList extends InlineComponent { +export default class MathList extends CompositeComponent { static componentType = "mathList"; - static renderChildren = true; + + static stateVariableToEvaluateAfterReplacements = + "readyToExpandWhenResolved"; static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -37,14 +39,22 @@ export default class MathList extends InlineComponent { attributes.maxNumber = { createComponentOfType: "number", createStateVariable: "maxNumber", - defaultValue: null, + defaultValue: Infinity, public: true, }; attributes.mergeMathLists = { createComponentOfType: "boolean", }; - Object.assign(attributes, returnRoundingAttributes()); + attributes.fixed = { + leaveRaw: true, + }; + + for (let attrName in returnRoundingAttributes()) { + attributes[attrName] = { + leaveRaw: true, + }; + } attributes.functionSymbols = { createComponentOfType: "textList", @@ -92,8 +102,26 @@ export default class MathList extends InlineComponent { }); sugarInstructions.push({ - replacementFunction: function ({ matchedChildren }) { - return groupIntoMathsSeparatedBySpaces({ matchedChildren }); + replacementFunction: function ({ + matchedChildren, + componentAttributes, + }) { + let result = groupIntoMathsSeparatedBySpaces({ + matchedChildren, + }); + + // Since an answer ignores composite descendants when calculating responses, + // we need to add isResponse from the mathList to its children. + if (componentAttributes.isResponse) { + for (let child of result.newChildren) { + if (!child.attributes) { + child.attributes = {}; + } + child.attributes.isResponse = { primitive: true }; + } + } + + return result; }, }); @@ -106,29 +134,12 @@ export default class MathList extends InlineComponent { group: "maths", componentTypes: ["math"], }, - { - group: "mathLists", - componentTypes: ["mathList"], - }, ]; } static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); - Object.assign( - stateVariableDefinitions, - returnRoundingStateVariableDefinitions(), - ); - - // set overrideChildHide so that children are hidden - // only based on whether or not the list is hidden - // so that can't have a list with partially hidden components - stateVariableDefinitions.overrideChildHide = { - returnDependencies: () => ({}), - definition: () => ({ setValue: { overrideChildHide: true } }), - }; - stateVariableDefinitions.mathsShadow = { defaultValue: null, hasEssential: true, @@ -140,6 +151,13 @@ export default class MathList extends InlineComponent { }), }; + stateVariableDefinitions.asList = { + returnDependencies: () => ({}), + definition() { + return { setValue: { asList: true } }; + }, + }; + stateVariableDefinitions.mergeMathLists = { public: true, shadowingInstructions: { @@ -151,11 +169,6 @@ export default class MathList extends InlineComponent { attributeName: "mergeMathLists", variableNames: ["value"], }, - mathListChildren: { - dependencyType: "child", - childGroups: ["mathLists"], - skipComponentNames: true, - }, mathChildren: { dependencyType: "child", childGroups: ["maths"], @@ -165,8 +178,7 @@ export default class MathList extends InlineComponent { definition({ dependencyValues }) { let mergeMathLists = dependencyValues.mergeMathListsAttr?.stateValues.value || - (dependencyValues.mathListChildren.length === 0 && - dependencyValues.mathChildren.length === 1); + dependencyValues.mathChildren.length === 1; return { setValue: { mergeMathLists } }; }, }; @@ -177,7 +189,7 @@ export default class MathList extends InlineComponent { createComponentOfType: "number", }, stateVariablesDeterminingDependencies: ["mergeMathLists"], - additionalStateVariablesDefined: ["childIndexByArrayKey"], + additionalStateVariablesDefined: ["childInfoByComponent"], returnDependencies({ stateValues }) { let dependencies = { maxNumber: { @@ -195,132 +207,78 @@ export default class MathList extends InlineComponent { }; if (stateValues.mergeMathLists) { - dependencies.mathAndMathListChildren = { + dependencies.mathChildren = { dependencyType: "child", - childGroups: ["maths", "mathLists"], - variableNames: ["value", "numComponents"], - variablesOptional: true, + childGroups: ["maths"], + variableNames: ["value"], }; } else { - dependencies.mathListChildren = { - dependencyType: "child", - childGroups: ["mathLists"], - variableNames: ["numComponents"], - }; - dependencies.mathAndMathListChildren = { + dependencies.mathChildren = { dependencyType: "child", - childGroups: ["maths", "mathLists"], - skipComponentNames: true, + childGroups: ["maths"], }; } return dependencies; }, - definition: function ({ dependencyValues, componentInfoObjects }) { + definition: function ({ dependencyValues }) { let numComponents = 0; - let childIndexByArrayKey = []; + let childInfoByComponent = []; - if (dependencyValues.mathAndMathListChildren.length > 0) { + if (dependencyValues.mathChildren.length > 0) { if (dependencyValues.mergeMathLists) { for (let [ childInd, child, - ] of dependencyValues.mathAndMathListChildren.entries()) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) - ) { - for ( - let i = 0; - i < child.stateValues.numComponents; - i++ - ) { - childIndexByArrayKey[numComponents + i] = [ - childInd, - i, - ]; - } - numComponents += - child.stateValues.numComponents; - } else { - let childValue = child.stateValues.value; + ] of dependencyValues.mathChildren.entries()) { + let childValue = child.stateValues.value; - if ( - childValue && - Array.isArray(childValue.tree) && - childValue.tree[0] === "list" - ) { - let nPieces = childValue.tree.length - 1; - for (let i = 0; i < nPieces; i++) { - childIndexByArrayKey[ - i + numComponents - ] = [childInd, i, nPieces]; - } - numComponents += nPieces; - } else { - childIndexByArrayKey[numComponents] = [ - childInd, - 0, - ]; - numComponents += 1; - } - } - } - } else { - let nMathLists = 0; - for (let [ - childInd, - child, - ] of dependencyValues.mathAndMathListChildren.entries()) { if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) + childValue && + Array.isArray(childValue.tree) && + childValue.tree[0] === "list" ) { - let mathListChild = - dependencyValues.mathListChildren[ - nMathLists - ]; - nMathLists++; - for ( - let i = 0; - i < mathListChild.stateValues.numComponents; - i++ - ) { - childIndexByArrayKey[numComponents + i] = [ + let nComponents = childValue.tree.length - 1; + for (let i = 0; i < nComponents; i++) { + childInfoByComponent[i + numComponents] = { childInd, - i, - ]; + component: i, + nComponents, + childName: child.componentName, + }; } - numComponents += - mathListChild.stateValues.numComponents; + numComponents += nComponents; } else { - childIndexByArrayKey[numComponents] = [ + childInfoByComponent[numComponents] = { childInd, - 0, - ]; + childName: child.componentName, + }; numComponents += 1; } } + } else { + numComponents = dependencyValues.mathChildren.length; + childInfoByComponent = + dependencyValues.mathChildren.map((child, i) => ({ + childInd: i, + childName: child.componentName, + })); } } else if (dependencyValues.mathsShadow !== null) { numComponents = dependencyValues.mathsShadow.length; } let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && numComponents > maxNum) { + if (numComponents > maxNum) { numComponents = maxNum; - childIndexByArrayKey = childIndexByArrayKey.slice( + childInfoByComponent = childInfoByComponent.slice( 0, maxNum, ); } return { - setValue: { numComponents, childIndexByArrayKey }, + setValue: { numComponents, childInfoByComponent }, checkForActualChange: { numComponents: true }, }; }, @@ -330,14 +288,12 @@ export default class MathList extends InlineComponent { public: true, shadowingInstructions: { createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), }, isArray: true, entryPrefixes: ["math"], stateVariablesDeterminingDependencies: [ "mergeMathLists", - "childIndexByArrayKey", + "childInfoByComponent", ], returnArraySizeDependencies: () => ({ numComponents: { @@ -356,9 +312,9 @@ export default class MathList extends InlineComponent { dependencyType: "stateVariable", variableName: "mergeMathLists", }, - childIndexByArrayKey: { + childInfoByComponent: { dependencyType: "stateVariable", - variableName: "childIndexByArrayKey", + variableName: "childInfoByComponent", }, mathsShadow: { dependencyType: "stateVariable", @@ -368,20 +324,16 @@ export default class MathList extends InlineComponent { for (let arrayKey of arrayKeys) { let childIndices = []; - let mathIndex = "1"; - if (stateValues.childIndexByArrayKey[arrayKey]) { + if (stateValues.childInfoByComponent[arrayKey]) { childIndices = [ - stateValues.childIndexByArrayKey[arrayKey][0], + stateValues.childInfoByComponent[arrayKey].childInd, ]; - mathIndex = - stateValues.childIndexByArrayKey[arrayKey][1] + 1; } dependenciesByKey[arrayKey] = { - mathAndMathListChildren: { + mathChildren: { dependencyType: "child", - childGroups: ["maths", "mathLists"], - variableNames: ["value", "math" + mathIndex], - variablesOptional: true, + childGroups: ["maths"], + variableNames: ["value"], childIndices, }, }; @@ -396,34 +348,22 @@ export default class MathList extends InlineComponent { let maths = {}; for (let arrayKey of arrayKeys) { - let child = - dependencyValuesByKey[arrayKey] - .mathAndMathListChildren[0]; + let child = dependencyValuesByKey[arrayKey].mathChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - let childValue = child.stateValues.value; - if ( - globalDependencyValues.mergeMathLists && - Array.isArray(childValue.tree) && - childValue.tree[0] === "list" - ) { - let ind2 = - globalDependencyValues.childIndexByArrayKey[ - arrayKey - ][1]; - maths[arrayKey] = - childValue.get_component(ind2); - } else { - maths[arrayKey] = childValue; - } - } else { - let mathIndex = - globalDependencyValues.childIndexByArrayKey[ + let childValue = child.stateValues.value; + if ( + globalDependencyValues.mergeMathLists && + Array.isArray(childValue.tree) && + childValue.tree[0] === "list" + ) { + let ind2 = + globalDependencyValues.childInfoByComponent[ arrayKey - ][1] + 1; - maths[arrayKey] = - child.stateValues["math" + mathIndex]; + ].component; + maths[arrayKey] = childValue.get_component(ind2); + } else { + maths[arrayKey] = childValue; } } else if (globalDependencyValues.mathsShadow !== null) { maths[arrayKey] = @@ -444,8 +384,8 @@ export default class MathList extends InlineComponent { if (globalDependencyValues.mergeMathLists) { let instructions = []; - let childIndexByArrayKey = - await stateValues.childIndexByArrayKey; + let childInfoByComponent = + await stateValues.childInfoByComponent; let arrayKeysAddressed = []; @@ -459,16 +399,19 @@ export default class MathList extends InlineComponent { } let desiredValue; - if (childIndexByArrayKey[arrayKey][2] !== undefined) { + if ( + childInfoByComponent[arrayKey].nComponents !== + undefined + ) { // found a math that has been split due to merging // array keys that are associated with this math child let firstInd = Number(arrayKey) - - childIndexByArrayKey[arrayKey][1]; + childInfoByComponent[arrayKey].component; let lastInd = firstInd + - childIndexByArrayKey[arrayKey][2] - + childInfoByComponent[arrayKey].nComponents - 1; // in case just one ind specified, merge with previous values @@ -508,29 +451,16 @@ export default class MathList extends InlineComponent { } let child = - dependencyValuesByKey[arrayKey] - .mathAndMathListChildren[0]; + dependencyValuesByKey[arrayKey].mathChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .mathAndMathListChildren, - desiredValue, - childIndex: 0, - variableIndex: 0, - }); - } else { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .mathAndMathListChildren, - desiredValue, - childIndex: 0, - variableIndex: 1, - }); - } + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey].mathChildren, + desiredValue, + childIndex: 0, + variableIndex: 0, + }); } } @@ -547,32 +477,17 @@ export default class MathList extends InlineComponent { continue; } - let child = - dependencyValuesByKey[arrayKey] - .mathAndMathListChildren[0]; + let child = dependencyValuesByKey[arrayKey].mathChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .mathAndMathListChildren, - desiredValue: - desiredStateVariableValues.maths[arrayKey], - childIndex: 0, - variableIndex: 0, - }); - } else { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .mathAndMathListChildren, - desiredValue: - desiredStateVariableValues.maths[arrayKey], - childIndex: 0, - variableIndex: 1, - }); - } + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey].mathChildren, + desiredValue: + desiredStateVariableValues.maths[arrayKey], + childIndex: 0, + variableIndex: 0, + }); } else if (globalDependencyValues.mathsShadow !== null) { if (!workspace.desiredMathShadow) { workspace.desiredMathShadow = [ @@ -595,36 +510,6 @@ export default class MathList extends InlineComponent { }, }; - stateVariableDefinitions.math = { - public: true, - shadowingInstructions: { - createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - }, - returnDependencies: () => ({ - maths: { - dependencyType: "stateVariable", - variableName: "maths", - }, - }), - definition({ dependencyValues }) { - let math; - if (dependencyValues.maths.length === 0) { - math = me.fromAst("\uff3f"); - } else if (dependencyValues.maths.length === 1) { - math = dependencyValues.maths[0]; - } else { - math = me.fromAst([ - "list", - ...dependencyValues.maths.map((x) => x.tree), - ]); - } - - return { setValue: { math } }; - }, - }; - stateVariableDefinitions.numValues = { isAlias: true, targetVariableName: "numComponents", @@ -635,473 +520,209 @@ export default class MathList extends InlineComponent { targetVariableName: "maths", }; - stateVariableDefinitions.numbers = { - public: true, - shadowingInstructions: { - createComponentOfType: "number", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - }, - isArray: true, - entryPrefixes: ["number"], - returnArraySizeDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - }), - returnArraySize({ dependencyValues }) { - return [dependencyValues.numComponents]; - }, - - returnArrayDependenciesByKey({ arrayKeys }) { - let dependenciesByKey = {}; - - for (let arrayKey of arrayKeys) { - dependenciesByKey[arrayKey] = { - math: { - dependencyType: "stateVariable", - variableName: `math${Number(arrayKey) + 1}`, - }, - }; - } - return { dependenciesByKey }; - }, - arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) { - let numbers = {}; - - for (let arrayKey of arrayKeys) { - numbers[arrayKey] = - dependencyValuesByKey[ - arrayKey - ].math.evaluate_to_constant(); - } - - return { setValue: { numbers } }; - }, - async inverseArrayDefinitionByKey({ - desiredStateVariableValues, - dependencyNamesByKey, - }) { - let instructions = []; - - for (let arrayKey in desiredStateVariableValues.numbers) { - instructions.push({ - setDependency: dependencyNamesByKey[arrayKey].math, - desiredValue: me.fromAst( - desiredStateVariableValues.numbers[arrayKey], - ), - }); - } - - return { - success: true, - instructions, - }; - }, - }; - - stateVariableDefinitions.latex = { - additionalStateVariablesDefined: ["latexs"], - public: true, - shadowingInstructions: { - createComponentOfType: "latex", - }, - forRenderer: true, + stateVariableDefinitions.readyToExpandWhenResolved = { returnDependencies: () => ({ - mathAndMathListChildren: { - dependencyType: "child", - childGroups: ["maths", "mathLists"], - variableNames: ["valueForDisplay", "latex", "latexs"], - variablesOptional: true, - }, - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - mergeMathLists: { - dependencyType: "stateVariable", - variableName: "mergeMathLists", - }, - mathsShadow: { - dependencyType: "stateVariable", - variableName: "mathsShadow", - }, - displayDigits: { + childInfoByComponent: { dependencyType: "stateVariable", - variableName: "displayDigits", - }, - displayDecimals: { - dependencyType: "stateVariable", - variableName: "displayDecimals", - }, - displaySmallAsZero: { - dependencyType: "stateVariable", - variableName: "displaySmallAsZero", - }, - padZeros: { - dependencyType: "stateVariable", - variableName: "padZeros", - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "mathList", - variableName: "numComponentsToDisplayByChild", + variableName: "childInfoByComponent", }, }), - definition: function ({ dependencyValues, componentName }) { - let latexs = []; - let params = {}; - if (dependencyValues.padZeros) { - if (Number.isFinite(dependencyValues.displayDecimals)) { - params.padToDecimals = dependencyValues.displayDecimals; - } - if (dependencyValues.displayDigits >= 1) { - params.padToDigits = dependencyValues.displayDigits; - } - } - if (dependencyValues.mathAndMathListChildren.length > 0) { - for (let child of dependencyValues.mathAndMathListChildren) { - if (child.stateValues.valueForDisplay) { - let childValue = child.stateValues.valueForDisplay; - - if ( - dependencyValues.mergeMathLists && - Array.isArray(childValue.tree) && - childValue.tree[0] === "list" - ) { - for ( - let i = 0; - i < childValue.tree.length - 1; - i++ - ) { - latexs.push( - childValue - .get_component(i) - .toLatex(params), - ); - } - } else { - latexs.push(child.stateValues.latex); - } - } else { - latexs.push(...child.stateValues.latexs); - } - } - } else if (dependencyValues.mathsShadow !== null) { - latexs = dependencyValues.mathsShadow.map((x) => - roundForDisplay({ - value: x, - dependencyValues, - }).toLatex(params), - ); - } - - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent mathList, which could have limited - // math of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } - - latexs = latexs.slice(0, numComponentsToDisplay); - - let latex = latexs.join(", "); - - return { setValue: { latex, latexs } }; + // When this state variable is marked stale + // it indicates we should update replacements. + // For this to work, must set + // stateVariableToEvaluateAfterReplacements + // to this variable so that it is marked fresh + markStale: () => ({ updateReplacements: true }), + definition: function () { + return { setValue: { readyToExpandWhenResolved: true } }; }, }; - stateVariableDefinitions.text = { - public: true, - shadowingInstructions: { - createComponentOfType: "text", - }, - additionalStateVariablesDefined: ["texts"], - returnDependencies: () => ({ - mathAndMathListChildren: { - dependencyType: "child", - childGroups: ["maths", "mathLists"], - variableNames: ["valueForDisplay", "text", "texts"], - variablesOptional: true, - }, - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - mergeMathLists: { - dependencyType: "stateVariable", - variableName: "mergeMathLists", - }, - mathsShadow: { - dependencyType: "stateVariable", - variableName: "mathsShadow", - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "mathList", - variableName: "numComponentsToDisplayByChild", - }, - }), - definition: function ({ dependencyValues, componentName }) { - let texts = []; - - if (dependencyValues.mathAndMathListChildren.length > 0) { - for (let child of dependencyValues.mathAndMathListChildren) { - if (child.stateValues.valueForDisplay) { - let childValue = child.stateValues.valueForDisplay; - - if ( - dependencyValues.mergeMathLists && - Array.isArray(childValue.tree) && - childValue.tree[0] === "list" - ) { - for ( - let i = 0; - i < childValue.tree.length - 1; - i++ - ) { - texts.push( - childValue.get_component(i).toString(), - ); - } - } else { - texts.push(child.stateValues.text); - } - } else { - texts.push(...child.stateValues.texts); - } - } - } else if (dependencyValues.mathsShadow !== null) { - texts = dependencyValues.mathsShadow.map((x) => - x.toString(), - ); - } - - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent mathList, which could have limited - // math of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } - - texts = texts.slice(0, numComponentsToDisplay); - - let text = texts.join(", "); - - return { setValue: { text, texts } }; - }, - }; + return stateVariableDefinitions; + } - stateVariableDefinitions.componentNamesInList = { - returnDependencies: () => ({ - mathAndMathListChildren: { - dependencyType: "child", - childGroups: ["maths", "mathLists"], - variableNames: ["componentNamesInList", "value"], - variablesOptional: true, - }, - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - mergeMathLists: { - dependencyType: "stateVariable", - variableName: "mergeMathLists", - }, - }), - definition: function ({ dependencyValues, componentInfoObjects }) { - let componentNamesInList = []; - let numComponentsLeft = dependencyValues.numComponents; - - for (let child of dependencyValues.mathAndMathListChildren) { - if (numComponentsLeft === 0) { - break; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) - ) { - let componentNamesToAdd = - child.stateValues.componentNamesInList.slice( - 0, - numComponentsLeft, - ); - - componentNamesInList.push(...componentNamesToAdd); - numComponentsLeft -= componentNamesToAdd.length; + static async createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }) { + let errors = []; + let warnings = []; + + let replacements = []; + let componentsCopied = []; + + let attributesToConvert = {}; + for (let attr of [ + "fixed", + ...Object.keys(returnRoundingAttributes()), + ]) { + if (attr in component.attributes) { + attributesToConvert[attr] = component.attributes[attr]; + } + } + + let newNamespace = component.attributes.newNamespace?.primitive; + + // allow one to override the fixed attributes + // as well as rounding settings + // by specifying it on the mathList + let attributesFromComposite = {}; + + if (Object.keys(attributesToConvert).length > 0) { + attributesFromComposite = convertAttributesForComponentType({ + attributes: attributesToConvert, + componentType: "math", + componentInfoObjects, + compositeCreatesNewNamespace: newNamespace, + }); + } + + let childInfoByComponent = + await component.stateValues.childInfoByComponent; + + if (childInfoByComponent.length > 0) { + for (let childInfo of childInfoByComponent) { + let replacementSource = components[childInfo.childName]; + + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); + + if (childInfo.nComponents !== undefined) { + replacements.push({ + componentType: "math", + attributes: JSON.parse( + JSON.stringify(attributesFromComposite), + ), + downstreamDependencies: { + [childInfo.childName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `x${childInfo.component + 1}`, + }, + ], + }, + }); } else { - componentNamesInList.push(child.componentName); - - if ( - dependencyValues.mergeMathLists && - Array.isArray(child.stateValues.value.tree) && - child.stateValues.value.tree[0] === "list" - ) { - numComponentsLeft -= - child.stateValues.value.tree.length - 1; - } else { - numComponentsLeft--; + let repl = await replacementSource.serialize({ + primitiveSourceAttributesToIgnore: ["isResponse"], + }); + if (!repl.attributes) { + repl.attributes = {}; } + Object.assign( + repl.attributes, + JSON.parse(JSON.stringify(attributesFromComposite)), + ); + replacements.push(repl); } } + } + } else { + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + replacements.push({ + componentType: "math", + attributes: JSON.parse( + JSON.stringify(attributesFromComposite), + ), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `math${i + 1}`, + }, + ], + }, + }); + } + } + + workspace.uniqueIdentifiersUsed = []; + replacements = postProcessCopy({ + serializedComponents: replacements, + componentName: component.componentName, + uniqueIdentifiersUsed: workspace.uniqueIdentifiersUsed, + addShadowDependencies: true, + markAsPrimaryShadow: true, + }); - return { setValue: { componentNamesInList } }; - }, - }; - - stateVariableDefinitions.numComponentsToDisplayByChild = { - additionalStateVariablesDefined: ["numChildrenToRender"], - returnDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - mathListChildren: { - dependencyType: "child", - childGroups: ["mathLists"], - variableNames: ["numComponents"], - }, - mathChildren: { - dependencyType: "child", - childGroups: ["maths"], - variableNames: ["value"], - }, - mathAndMathListChildren: { - dependencyType: "child", - childGroups: ["maths", "mathLists"], - skipComponentNames: true, - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "mathList", - variableName: "numComponentsToDisplayByChild", - }, - mergeMathLists: { - dependencyType: "stateVariable", - variableName: "mergeMathLists", - }, - }), - definition: function ({ - dependencyValues, - componentInfoObjects, - componentName, - }) { - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent mathList, which could have limited - // math of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } - - let numComponentsToDisplayByChild = {}; + let processResult = processAssignNames({ + assignNames: component.doenetAttributes.assignNames, + serializedComponents: replacements, + parentName: component.componentName, + parentCreatesNewNamespace: newNamespace, + componentInfoObjects, + }); + errors.push(...processResult.errors); + warnings.push(...processResult.warnings); - let numComponentsSoFar = 0; - let numChildrenToRender = 0; + workspace.componentsCopied = componentsCopied; - let nMathLists = 0; - let nMaths = 0; - for (let child of dependencyValues.mathAndMathListChildren) { - let numComponentsLeft = Math.max( - 0, - numComponentsToDisplay - numComponentsSoFar, - ); - if (numComponentsLeft > 0) { - numChildrenToRender++; - } - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) - ) { - let mathListChild = - dependencyValues.mathListChildren[nMathLists]; - nMathLists++; - - let numComponentsForMathListChild = Math.min( - numComponentsLeft, - mathListChild.stateValues.numComponents, - ); + return { + replacements: processResult.serializedComponents, + errors, + warnings, + }; + } - numComponentsToDisplayByChild[ - mathListChild.componentName - ] = numComponentsForMathListChild; - numComponentsSoFar += numComponentsForMathListChild; - } else { - let mathChild = dependencyValues.mathChildren[nMaths]; - nMaths++; + static async calculateReplacementChanges({ + component, + components, + componentInfoObjects, + workspace, + }) { + // TODO: don't yet have a way to return errors and warnings! + let errors = []; + let warnings = []; + + let componentsToCopy = []; + + let childInfoByComponent = + await component.stateValues.childInfoByComponent; + + for (let childInfo of childInfoByComponent) { + let replacementSource = components[childInfo.childName]; + + if (replacementSource) { + componentsToCopy.push(replacementSource.componentName); + } + } + + if ( + componentsToCopy.length == workspace.componentsCopied.length && + workspace.componentsCopied.every( + (x, i) => x === componentsToCopy[i], + ) + ) { + return []; + } + + // for now, just recreate + let replacementResults = await this.createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }); - if ( - dependencyValues.mergeMathLists && - Array.isArray(mathChild.stateValues.value.tree) && - mathChild.stateValues.value.tree[0] === "list" - ) { - let numComponentsInMath = - mathChild.stateValues.value.tree.length - 1; - - if (numComponentsLeft < numComponentsInMath) { - numComponentsToDisplayByChild[ - mathChild.componentName - ] = numComponentsLeft; - numComponentsSoFar += numComponentsLeft; - } else { - // if we will display the whole math list, - // don't set numComponentsToDisplayByChild for the math child - numComponentsSoFar += numComponentsInMath; - } - } else { - numComponentsSoFar += 1; - } - } - } + let replacements = replacementResults.replacements; + errors.push(...replacementResults.errors); + warnings.push(...replacementResults.warnings); - return { - setValue: { - numComponentsToDisplayByChild, - numChildrenToRender, - }, - }; + let replacementChanges = [ + { + changeType: "add", + changeTopLevelReplacements: true, + firstReplacementInd: 0, + numberReplacementsToReplace: component.replacements.length, + serializedReplacements: replacements, }, - markStale: () => ({ updateRenderedChildren: true }), - }; + ]; - return stateVariableDefinitions; + return replacementChanges; } - - static adapters = [ - { - stateVariable: "math", - stateVariablesToShadow: Object.keys( - returnRoundingStateVariableDefinitions(), - ), - }, - { - stateVariable: "numbers", - componentType: "numberList", - stateVariablesToShadow: Object.keys( - returnRoundingStateVariableDefinitions(), - ), - }, - "text", - ]; } diff --git a/packages/doenetml-worker/src/components/MathOperators.js b/packages/doenetml-worker/src/components/MathOperators.js index 0d51ee7a5..24d3623a1 100644 --- a/packages/doenetml-worker/src/components/MathOperators.js +++ b/packages/doenetml-worker/src/components/MathOperators.js @@ -286,10 +286,9 @@ export class Round extends MathBaseOperatorOneInput { // change default rounding to 14 // so that actual rounding result can be seen. - // Don't include maths for childsGroupIfSingleMatch + // Don't include maths for childGroupsIfSingleMatch // so that this overrides display rounding from children let roundingDefinitions = returnRoundingStateVariableDefinitions({ - includeListParents: true, displayDigitsDefault: 14, }); Object.assign(stateVariableDefinitions, roundingDefinitions); diff --git a/packages/doenetml-worker/src/components/Number.js b/packages/doenetml-worker/src/components/Number.js index 113b4d02a..dab706b48 100644 --- a/packages/doenetml-worker/src/components/Number.js +++ b/packages/doenetml-worker/src/components/Number.js @@ -144,9 +144,8 @@ export default class NumberComponent extends InlineComponent { Object.assign(stateVariableDefinitions, anchorDefinition); let roundingDefinitions = returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch: ["maths", "numbers"], + childGroupsIfSingleMatch: ["maths", "numbers"], childGroupsToStopSingleMatch: ["strings", "texts", "booleans"], - includeListParents: true, }); Object.assign(stateVariableDefinitions, roundingDefinitions); @@ -296,7 +295,7 @@ export default class NumberComponent extends InlineComponent { for (let child of dependencyValues.allChildren) { if (typeof child !== "string") { - // a math, mathList, text, textList, boolean, or booleanList + // a math, number, text, or boolean let code = codePre + subnum; if ( diff --git a/packages/doenetml-worker/src/components/NumberList.js b/packages/doenetml-worker/src/components/NumberList.js index 648831769..059ed090f 100644 --- a/packages/doenetml-worker/src/components/NumberList.js +++ b/packages/doenetml-worker/src/components/NumberList.js @@ -1,16 +1,17 @@ -import { roundForDisplay } from "../utils/math"; -import { - returnRoundingAttributeComponentShadowing, - returnRoundingAttributes, - returnRoundingStateVariableDefinitions, -} from "../utils/rounding"; -import InlineComponent from "./abstract/InlineComponent"; +import CompositeComponent from "./abstract/CompositeComponent"; +import { returnRoundingAttributes } from "../utils/rounding"; import { returnGroupIntoComponentTypeSeparatedBySpacesOutsideParens } from "./commonsugar/lists"; -import me from "math-expressions"; +import { + convertAttributesForComponentType, + postProcessCopy, +} from "../utils/copy"; +import { processAssignNames } from "../utils/naming"; -export default class NumberList extends InlineComponent { +export default class NumberList extends CompositeComponent { static componentType = "numberList"; - static renderChildren = true; + + static stateVariableToEvaluateAfterReplacements = + "readyToExpandWhenResolved"; static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -33,11 +34,19 @@ export default class NumberList extends InlineComponent { attributes.maxNumber = { createComponentOfType: "number", createStateVariable: "maxNumber", - defaultValue: null, + defaultValue: Infinity, public: true, }; - Object.assign(attributes, returnRoundingAttributes()); + attributes.fixed = { + leaveRaw: true, + }; + + for (let attrName in returnRoundingAttributes()) { + attributes[attrName] = { + leaveRaw: true, + }; + } return attributes; } @@ -54,8 +63,26 @@ export default class NumberList extends InlineComponent { }); sugarInstructions.push({ - replacementFunction: function ({ matchedChildren }) { - return groupIntoNumbersSeparatedBySpaces({ matchedChildren }); + replacementFunction: function ({ + matchedChildren, + componentAttributes, + }) { + let result = groupIntoNumbersSeparatedBySpaces({ + matchedChildren, + }); + + // Since an answer ignores composite descendants when calculating responses, + // we need to add isResponse from the numberList to its children. + if (componentAttributes.isResponse) { + for (let child of result.newChildren) { + if (!child.attributes) { + child.attributes = {}; + } + child.attributes.isResponse = { primitive: true }; + } + } + + return result; }, }); @@ -68,29 +95,12 @@ export default class NumberList extends InlineComponent { group: "numbers", componentTypes: ["number"], }, - { - group: "numberLists", - componentTypes: ["numberList"], - }, ]; } static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); - Object.assign( - stateVariableDefinitions, - returnRoundingStateVariableDefinitions(), - ); - - // set overrideChildHide so that children are hidden - // only based on whether or not the list is hidden - // so that can't have a list with partially hidden components - stateVariableDefinitions.overrideChildHide = { - returnDependencies: () => ({}), - definition: () => ({ setValue: { overrideChildHide: true } }), - }; - stateVariableDefinitions.numbersShadow = { defaultValue: null, hasEssential: true, @@ -102,100 +112,72 @@ export default class NumberList extends InlineComponent { }), }; + stateVariableDefinitions.asList = { + returnDependencies: () => ({}), + definition() { + return { setValue: { asList: true } }; + }, + }; + stateVariableDefinitions.numComponents = { public: true, shadowingInstructions: { createComponentOfType: "number", }, - additionalStateVariablesDefined: ["childIndexByArrayKey"], + additionalStateVariablesDefined: ["childNameByComponent"], returnDependencies: () => ({ maxNumber: { dependencyType: "stateVariable", variableName: "maxNumber", }, - numberListChildren: { + numberChildren: { dependencyType: "child", - childGroups: ["numberLists"], - variableNames: ["numComponents"], - }, - numberAndNumberListChildren: { - dependencyType: "child", - childGroups: ["numbers", "numberLists"], - skipComponentNames: true, + childGroups: ["numbers"], }, numbersShadow: { dependencyType: "stateVariable", variableName: "numbersShadow", }, }), - definition: function ({ dependencyValues, componentInfoObjects }) { + definition: function ({ dependencyValues }) { let numComponents = 0; - let childIndexByArrayKey = []; - - let nNumberLists = 0; - if (dependencyValues.numberAndNumberListChildren.length > 0) { - for (let [ - childInd, - child, - ] of dependencyValues.numberAndNumberListChildren.entries()) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - let numberListChild = - dependencyValues.numberListChildren[ - nNumberLists - ]; - nNumberLists++; - for ( - let i = 0; - i < numberListChild.stateValues.numComponents; - i++ - ) { - childIndexByArrayKey[numComponents + i] = [ - childInd, - i, - ]; - } - numComponents += - numberListChild.stateValues.numComponents; - } else { - childIndexByArrayKey[numComponents] = [childInd, 0]; - numComponents += 1; - } - } + let childNameByComponent = []; + + if (dependencyValues.numberChildren.length > 0) { + childNameByComponent = dependencyValues.numberChildren.map( + (x) => x.componentName, + ); + numComponents = dependencyValues.numberChildren.length; } else if (dependencyValues.numbersShadow !== null) { numComponents = dependencyValues.numbersShadow.length; } let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && numComponents > maxNum) { + if (numComponents > maxNum) { numComponents = maxNum; - childIndexByArrayKey = childIndexByArrayKey.slice( + childNameByComponent = childNameByComponent.slice( 0, maxNum, ); } return { - setValue: { numComponents, childIndexByArrayKey }, + setValue: { + numComponents, + childNameByComponent, + }, checkForActualChange: { numComponents: true }, }; }, }; stateVariableDefinitions.numbers = { - public: true, shadowingInstructions: { createComponentOfType: "number", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), }, isArray: true, entryPrefixes: ["number"], - stateVariablesDeterminingDependencies: ["childIndexByArrayKey"], + stateVariablesDeterminingDependencies: ["childNameByComponent"], returnArraySizeDependencies: () => ({ numComponents: { dependencyType: "stateVariable", @@ -209,9 +191,9 @@ export default class NumberList extends InlineComponent { returnArrayDependenciesByKey({ arrayKeys, stateValues }) { let dependenciesByKey = {}; let globalDependencies = { - childIndexByArrayKey: { + childNameByComponent: { dependencyType: "stateVariable", - variableName: "childIndexByArrayKey", + variableName: "childNameByComponent", }, numbersShadow: { dependencyType: "stateVariable", @@ -221,20 +203,14 @@ export default class NumberList extends InlineComponent { for (let arrayKey of arrayKeys) { let childIndices = []; - let numberIndex = "1"; - if (stateValues.childIndexByArrayKey[arrayKey]) { - childIndices = [ - stateValues.childIndexByArrayKey[arrayKey][0], - ]; - numberIndex = - stateValues.childIndexByArrayKey[arrayKey][1] + 1; + if (stateValues.childNameByComponent[arrayKey]) { + childIndices = [arrayKey]; } dependenciesByKey[arrayKey] = { - numberAndNumberListChildren: { + numberChildren: { dependencyType: "child", - childGroups: ["numbers", "numberLists"], - variableNames: ["value", "number" + numberIndex], - variablesOptional: true, + childGroups: ["numbers"], + variableNames: ["value"], childIndices, }, }; @@ -250,20 +226,10 @@ export default class NumberList extends InlineComponent { for (let arrayKey of arrayKeys) { let child = - dependencyValuesByKey[arrayKey] - .numberAndNumberListChildren[0]; + dependencyValuesByKey[arrayKey].numberChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - numbers[arrayKey] = child.stateValues.value; - } else { - let numberIndex = - globalDependencyValues.childIndexByArrayKey[ - arrayKey - ][1] + 1; - numbers[arrayKey] = - child.stateValues["number" + numberIndex]; - } + numbers[arrayKey] = child.stateValues.value; } else if (globalDependencyValues.numbersShadow !== null) { numbers[arrayKey] = globalDependencyValues.numbersShadow[arrayKey]; @@ -287,35 +253,17 @@ export default class NumberList extends InlineComponent { } let child = - dependencyValuesByKey[arrayKey] - .numberAndNumberListChildren[0]; + dependencyValuesByKey[arrayKey].numberChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .numberAndNumberListChildren, - desiredValue: - desiredStateVariableValues.numbers[ - arrayKey - ], - childIndex: 0, - variableIndex: 0, - }); - } else { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .numberAndNumberListChildren, - desiredValue: - desiredStateVariableValues.numbers[ - arrayKey - ], - childIndex: 0, - variableIndex: 1, - }); - } + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey].numberChildren, + desiredValue: + desiredStateVariableValues.numbers[arrayKey], + childIndex: 0, + variableIndex: 0, + }); } else if (globalDependencyValues.numbersShadow !== null) { if (!workspace.desiredNumberShadow) { workspace.desiredNumberShadow = [ @@ -348,337 +296,192 @@ export default class NumberList extends InlineComponent { targetVariableName: "numbers", }; - stateVariableDefinitions.math = { - public: true, - shadowingInstructions: { - createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - }, + stateVariableDefinitions.readyToExpandWhenResolved = { returnDependencies: () => ({ - numbers: { + childNameByComponent: { dependencyType: "stateVariable", - variableName: "numbers", + variableName: "childNameByComponent", }, }), - definition({ dependencyValues }) { - let math; - if (dependencyValues.numbers.length === 0) { - math = me.fromAst("\uff3f"); - } else if (dependencyValues.numbers.length === 1) { - math = me.fromAst(dependencyValues.numbers[0]); - } else { - math = me.fromAst(["list", ...dependencyValues.numbers]); - } - - return { setValue: { math } }; + // When this state variable is marked stale + // it indicates we should update replacements. + // For this to work, must set + // stateVariableToEvaluateAfterReplacements + // to this variable so that it is marked fresh + markStale: () => ({ updateReplacements: true }), + definition: function () { + return { setValue: { readyToExpandWhenResolved: true } }; }, }; - stateVariableDefinitions.maths = { - public: true, - shadowingInstructions: { - createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - }, - isArray: true, - entryPrefixes: ["math"], - returnArraySizeDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - }), - returnArraySize({ dependencyValues }) { - return [dependencyValues.numComponents]; - }, + return stateVariableDefinitions; + } - returnArrayDependenciesByKey({ arrayKeys }) { - let dependenciesByKey = {}; + static async createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }) { + let errors = []; + let warnings = []; + + let replacements = []; + let componentsCopied = []; + + let attributesToConvert = {}; + for (let attr of [ + "fixed", + "isResponse", + ...Object.keys(returnRoundingAttributes()), + ]) { + if (attr in component.attributes) { + attributesToConvert[attr] = component.attributes[attr]; + } + } + + let newNamespace = component.attributes.newNamespace?.primitive; + + // allow one to override the fixed and isResponse attributes + // as well as rounding settings + // by specifying it on the sequence + let attributesFromComposite = {}; + + if (Object.keys(attributesToConvert).length > 0) { + attributesFromComposite = convertAttributesForComponentType({ + attributes: attributesToConvert, + componentType: "number", + componentInfoObjects, + compositeCreatesNewNamespace: newNamespace, + }); + } - for (let arrayKey of arrayKeys) { - dependenciesByKey[arrayKey] = { - number: { - dependencyType: "stateVariable", - variableName: `number${Number(arrayKey) + 1}`, - }, - }; - } - return { dependenciesByKey }; - }, - arrayDefinitionByKey({ dependencyValuesByKey, arrayKeys }) { - let maths = {}; + let childNameByComponent = + await component.stateValues.childNameByComponent; - for (let arrayKey of arrayKeys) { - maths[arrayKey] = me.fromAst( - dependencyValuesByKey[arrayKey].number, - ); - } + if (childNameByComponent.length > 0) { + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; - return { setValue: { maths } }; - }, - async inverseArrayDefinitionByKey({ - desiredStateVariableValues, - dependencyNamesByKey, - }) { - let instructions = []; + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); - for (let arrayKey in desiredStateVariableValues.maths) { - instructions.push({ - setDependency: dependencyNamesByKey[arrayKey].number, - desiredValue: - desiredStateVariableValues.maths[ - arrayKey - ].evaluate_to_constant(), + let repl = await replacementSource.serialize({ + primitiveSourceAttributesToIgnore: ["isResponse"], }); - } - - return { - success: true, - instructions, - }; - }, - }; - - stateVariableDefinitions.text = { - public: true, - forRenderer: true, - shadowingInstructions: { - createComponentOfType: "text", - }, - additionalStateVariablesDefined: ["texts"], - returnDependencies: () => ({ - numberAndNumberListChildren: { - dependencyType: "child", - childGroups: ["numbers", "numberLists"], - variableNames: ["valueForDisplay", "text", "texts"], - variablesOptional: true, - }, - numbersShadow: { - dependencyType: "stateVariable", - variableName: "numbersShadow", - }, - displayDigits: { - dependencyType: "stateVariable", - variableName: "displayDigits", - }, - displayDecimals: { - dependencyType: "stateVariable", - variableName: "displayDecimals", - }, - displaySmallAsZero: { - dependencyType: "stateVariable", - variableName: "displaySmallAsZero", - }, - padZeros: { - dependencyType: "stateVariable", - variableName: "padZeros", - }, - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "numberList", - variableName: "numComponentsToDisplayByChild", - }, - }), - definition: function ({ dependencyValues, componentName }) { - let texts = []; - let params = {}; - if (dependencyValues.padZeros) { - if (Number.isFinite(dependencyValues.displayDecimals)) { - params.padToDecimals = dependencyValues.displayDecimals; - } - if (dependencyValues.displayDigits >= 1) { - params.padToDigits = dependencyValues.displayDigits; - } - } - if (dependencyValues.numberAndNumberListChildren.length > 0) { - for (let child of dependencyValues.numberAndNumberListChildren) { - if (child.stateValues.valueForDisplay !== undefined) { - texts.push(child.stateValues.text); - } else { - texts.push(...child.stateValues.texts); - } + if (!repl.attributes) { + repl.attributes = {}; } - } else if (dependencyValues.numbersShadow !== null) { - texts = dependencyValues.numbersShadow.map((x) => - roundForDisplay({ - value: me.fromAst(x), - dependencyValues, - }).toString(params), + Object.assign( + repl.attributes, + JSON.parse(JSON.stringify(attributesFromComposite)), ); + replacements.push(repl); } + } + } else { + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + replacements.push({ + componentType: "number", + attributes: JSON.parse( + JSON.stringify(attributesFromComposite), + ), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `number${i + 1}`, + }, + ], + }, + }); + } + } + + workspace.uniqueIdentifiersUsed = []; + replacements = postProcessCopy({ + serializedComponents: replacements, + componentName: component.componentName, + uniqueIdentifiersUsed: workspace.uniqueIdentifiersUsed, + addShadowDependencies: true, + markAsPrimaryShadow: true, + }); - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent numberList, which could have limited - // number of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } - texts = texts.slice(0, numComponentsToDisplay); - - let text = texts.join(", "); - - return { setValue: { text, texts } }; - }, - }; - - stateVariableDefinitions.componentNamesInList = { - returnDependencies: () => ({ - numberAndNumberListChildren: { - dependencyType: "child", - childGroups: ["numbers", "numberLists"], - variableNames: ["componentNamesInList"], - variablesOptional: true, - }, - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - }), - definition: function ({ dependencyValues, componentInfoObjects }) { - let componentNamesInList = []; - - for (let child of dependencyValues.numberAndNumberListChildren) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - componentNamesInList.push( - ...child.stateValues.componentNamesInList, - ); - } else { - componentNamesInList.push(child.componentName); - } - } + let processResult = processAssignNames({ + assignNames: component.doenetAttributes.assignNames, + serializedComponents: replacements, + parentName: component.componentName, + parentCreatesNewNamespace: newNamespace, + componentInfoObjects, + }); + errors.push(...processResult.errors); + warnings.push(...processResult.warnings); - componentNamesInList = componentNamesInList.slice( - 0, - dependencyValues.numComponents, - ); + workspace.componentsCopied = componentsCopied; - return { setValue: { componentNamesInList } }; - }, + return { + replacements: processResult.serializedComponents, + errors, + warnings, }; + } - stateVariableDefinitions.numComponentsToDisplayByChild = { - additionalStateVariablesDefined: ["numChildrenToRender"], - returnDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - numberListChildren: { - dependencyType: "child", - childGroups: ["numberLists"], - variableNames: ["numComponents"], - }, - numberAndNumberListChildren: { - dependencyType: "child", - childGroups: ["numbers", "numberLists"], - skipComponentNames: true, - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "numberList", - variableName: "numComponentsToDisplayByChild", - }, - }), - definition: function ({ - dependencyValues, - componentInfoObjects, - componentName, - }) { - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent numberList, which could have limited - // number of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } - - let numComponentsToDisplayByChild = {}; - - let numComponentsSoFar = 0; - let numChildrenToRender = 0; + static async calculateReplacementChanges({ + component, + components, + componentInfoObjects, + workspace, + }) { + // TODO: don't yet have a way to return errors and warnings! + let errors = []; + let warnings = []; + + let componentsToCopy = []; + + let childNameByComponent = + await component.stateValues.childNameByComponent; + + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; + + if (replacementSource) { + componentsToCopy.push(replacementSource.componentName); + } + } + + if ( + componentsToCopy.length == workspace.componentsCopied.length && + workspace.componentsCopied.every( + (x, i) => x === componentsToCopy[i], + ) + ) { + return []; + } + + // for now, just recreate + let replacementResults = await this.createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }); - let nNumberLists = 0; - for (let child of dependencyValues.numberAndNumberListChildren) { - let numComponentsLeft = Math.max( - 0, - numComponentsToDisplay - numComponentsSoFar, - ); - if (numComponentsLeft > 0) { - numChildrenToRender++; - } - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - let numberListChild = - dependencyValues.numberListChildren[nNumberLists]; - nNumberLists++; - - let numComponentsForNumberListChild = Math.min( - numComponentsLeft, - numberListChild.stateValues.numComponents, - ); - - numComponentsToDisplayByChild[ - numberListChild.componentName - ] = numComponentsForNumberListChild; - numComponentsSoFar += numComponentsForNumberListChild; - } else { - numComponentsSoFar += 1; - } - } + let replacements = replacementResults.replacements; + errors.push(...replacementResults.errors); + warnings.push(...replacementResults.warnings); - return { - setValue: { - numComponentsToDisplayByChild, - numChildrenToRender, - }, - }; + let replacementChanges = [ + { + changeType: "add", + changeTopLevelReplacements: true, + firstReplacementInd: 0, + numberReplacementsToReplace: component.replacements.length, + serializedReplacements: replacements, }, - markStale: () => ({ updateRenderedChildren: true }), - }; + ]; - return stateVariableDefinitions; + return replacementChanges; } - - static adapters = [ - { - stateVariable: "maths", - componentType: "mathList", - stateVariablesToShadow: Object.keys( - returnRoundingStateVariableDefinitions(), - ), - }, - { - stateVariable: "math", - stateVariablesToShadow: Object.keys( - returnRoundingStateVariableDefinitions(), - ), - }, - "text", - ]; } diff --git a/packages/doenetml-worker/src/components/Substitute.js b/packages/doenetml-worker/src/components/Substitute.js index 3347f6da2..1e9381aa9 100644 --- a/packages/doenetml-worker/src/components/Substitute.js +++ b/packages/doenetml-worker/src/components/Substitute.js @@ -122,7 +122,7 @@ export default class Substitute extends CompositeComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); let roundingDefinitions = returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch: ["anything"], + childGroupsIfSingleMatch: ["anything"], }); Object.assign(stateVariableDefinitions, roundingDefinitions); diff --git a/packages/doenetml-worker/src/components/TupleList.js b/packages/doenetml-worker/src/components/TupleList.js index 1fd4a9114..a0fe97168 100644 --- a/packages/doenetml-worker/src/components/TupleList.js +++ b/packages/doenetml-worker/src/components/TupleList.js @@ -3,7 +3,6 @@ import { breakEmbeddedStringsIntoParensPieces } from "./commonsugar/breakstrings export default class TupleList extends MathList { static componentType = "tupleList"; - static rendererType = "mathList"; static includeBlankStringChildren = false; diff --git a/packages/doenetml-worker/src/components/abstract/MathBaseOperator.js b/packages/doenetml-worker/src/components/abstract/MathBaseOperator.js index b88298846..1175d1ef4 100644 --- a/packages/doenetml-worker/src/components/abstract/MathBaseOperator.js +++ b/packages/doenetml-worker/src/components/abstract/MathBaseOperator.js @@ -79,14 +79,6 @@ export default class MathOperator extends MathComponent { group: "numbers", componentTypes: ["number"], }, - { - group: "mathLists", - componentTypes: ["mathList"], - }, - { - group: "numberLists", - componentTypes: ["numberList"], - }, ]; } @@ -94,13 +86,7 @@ export default class MathOperator extends MathComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); let roundingDefinitions = returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch: [ - "maths", - "numbers", - "mathLists", - "numberLists", - ], - includeListParents: true, + childGroupsIfSingleMatch: ["maths", "numbers"], }); Object.assign(stateVariableDefinitions, roundingDefinitions); @@ -120,11 +106,6 @@ export default class MathOperator extends MathComponent { variableNames: ["isNumber"], variablesOptional: true, }, - mathListChildren: { - dependencyType: "child", - childGroups: ["mathLists"], - variableNames: ["maths"], - }, shadowSource: { dependencyType: "shadowSource", variableNames: ["isNumericOperator"], @@ -136,10 +117,7 @@ export default class MathOperator extends MathComponent { isNumericOperator = true; } else if (dependencyValues.forceSymbolic) { isNumericOperator = false; - } else if ( - dependencyValues.mathChildren.length === 0 && - dependencyValues.mathListChildren.length === 0 - ) { + } else if (dependencyValues.mathChildren.length === 0) { isNumericOperator = dependencyValues.shadowSource?.stateValues .isNumericOperator; @@ -149,15 +127,9 @@ export default class MathOperator extends MathComponent { } else { // have math children and aren't forced to be numeric or symbolic // will be numeric only if have all math children are numbers - isNumericOperator = - dependencyValues.mathChildren.every( - (x) => x.stateValues.isNumber, - ) && - dependencyValues.mathListChildren.every((x) => - x.stateValues.maths.every((y) => - Number.isFinite(y.tree), - ), - ); + isNumericOperator = dependencyValues.mathChildren.every( + (x) => x.stateValues.isNumber, + ); } return { setValue: { isNumericOperator } }; @@ -198,19 +170,8 @@ export default class MathOperator extends MathComponent { returnDependencies: () => ({ mathNumberChildren: { dependencyType: "child", - childGroups: [ - "maths", - "numbers", - "mathLists", - "numberLists", - ], - variableNames: [ - "value", - "maths", - "numbers", - "canBeModified", - ], - variablesOptional: true, + childGroups: ["maths", "numbers"], + variableNames: ["value", "canBeModified"], }, isNumericOperator: { dependencyType: "stateVariable", @@ -248,32 +209,11 @@ export default class MathOperator extends MathComponent { }) ) { inputs.push(child.stateValues.value); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "math", - }) - ) { + } else { + // math let value = child.stateValues.value.evaluate_to_constant(); inputs.push(value); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - inputs.push(...child.stateValues.numbers); - } else { - // mathLIst - let values = child.stateValues.maths.map((x) => { - let value = x.evaluate_to_constant(); - if (!Number.isFinite(value)) { - value = NaN; - } - return value; - }); - inputs.push(...values); } } @@ -294,27 +234,9 @@ export default class MathOperator extends MathComponent { }) ) { inputs.push(me.fromAst(child.stateValues.value)); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "math", - }) - ) { - inputs.push(child.stateValues.value); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - inputs.push( - ...child.stateValues.numbers.map((x) => - me.fromAst(x), - ), - ); } else { - // mathList - inputs.push(...child.stateValues.maths); + // math + inputs.push(child.stateValues.value); } } @@ -353,12 +275,8 @@ export default class MathOperator extends MathComponent { child.stateValues.canBeModified, ); inputToChildIndex.push(childInd); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "math", - }) - ) { + } else { + // math let value = child.stateValues.value.evaluate_to_constant(); inputs.push(value); @@ -366,52 +284,6 @@ export default class MathOperator extends MathComponent { child.stateValues.canBeModified, ); inputToChildIndex.push(childInd); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - inputs.push(...child.stateValues.numbers); - canBeModified.push( - ...Array( - child.stateValues.numbers.length, - ).fill(child.stateValues.canBeModified), - ); - if (child.stateValues.numbers.length === 1) { - inputToChildIndex.push(childInd); - } else { - // TODO: invert entries of numberlist that isn't length 1? - inputToChildIndex.push( - ...Array( - child.stateValues.numbers.length, - ).fill(NaN), - ); - } - } else { - // mathList - let values = child.stateValues.maths.map( - (x) => { - let value = x.evaluate_to_constant(); - return value; - }, - ); - inputs.push(...values); - canBeModified.push( - ...Array( - child.stateValues.maths.length, - ).fill(child.stateValues.canBeModified), - ); - if (child.stateValues.maths.length === 1) { - inputToChildIndex.push(childInd); - } else { - // TODO: invert entries of mathlist that isn't length 1? - inputToChildIndex.push( - ...Array( - child.stateValues.maths.length, - ).fill(NaN), - ); - } } } let results = dependencyValues.inverseNumericOperator({ @@ -428,38 +300,7 @@ export default class MathOperator extends MathComponent { inputToChildIndex[results.inputNumber]; if (Number.isFinite(childIndex)) { let desiredValue = results.inputValue; - let variableIndex = 0; - - let child = - dependencyValues.mathNumberChildren[ - childIndex - ]; - if ( - componentInfoObjects.isInheritedComponentType( - { - inheritedComponentType: - child.componentType, - baseComponentType: "numberList", - }, - ) - ) { - variableIndex = 2; - // if had childIndex, must have been just one number - desiredValue = { 0: desiredValue }; - } else if ( - componentInfoObjects.isInheritedComponentType( - { - inheritedComponentType: - child.componentType, - baseComponentType: "mathList", - }, - ) - ) { - variableIndex = 1; - // if had childIndex, must have been just one math - desiredValue = { 0: desiredValue }; - } return { success: true, instructions: [ @@ -467,7 +308,7 @@ export default class MathOperator extends MathComponent { setDependency: "mathNumberChildren", desiredValue, childIndex, - variableIndex, + variableIndex: 0, }, ], }; @@ -497,59 +338,11 @@ export default class MathOperator extends MathComponent { inputs.push(me.fromAst(child.stateValues.value)); canBeModified.push(child.stateValues.canBeModified); inputToChildIndex.push(childInd); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "math", - }) - ) { + } else { + // math inputs.push(child.stateValues.value); canBeModified.push(child.stateValues.canBeModified); inputToChildIndex.push(childInd); - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - inputs.push( - ...child.stateValues.numbers.map((x) => - me.fromAst(x), - ), - ); - canBeModified.push( - ...Array(child.stateValues.numbers.length).fill( - child.stateValues.canBeModified, - ), - ); - if (child.stateValues.numbers.length === 1) { - inputToChildIndex.push(childInd); - } else { - // TODO: invert entries of numberlist that isn't length 1? - inputToChildIndex.push( - ...Array( - child.stateValues.numbers.length, - ).fill(NaN), - ); - } - } else { - // mathList - inputs.push(...child.stateValues.maths); - canBeModified.push( - ...Array(child.stateValues.maths.length).fill( - child.stateValues.canBeModified, - ), - ); - if (child.stateValues.maths.length === 1) { - inputToChildIndex.push(childInd); - } else { - // TODO: invert entries of mathlist that isn't length 1? - inputToChildIndex.push( - ...Array( - child.stateValues.maths.length, - ).fill(NaN), - ); - } } } @@ -564,30 +357,6 @@ export default class MathOperator extends MathComponent { let childIndex = inputToChildIndex[results.inputNumber]; if (Number.isFinite(childIndex)) { let desiredValue = results.inputValue; - let variableIndex = 0; - - let child = - dependencyValues.mathNumberChildren[childIndex]; - - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - variableIndex = 2; - // if had childIndex, must have been just one number - desiredValue = { 0: desiredValue }; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) - ) { - variableIndex = 1; - // if had childIndex, must have been just one math - desiredValue = { 0: desiredValue }; - } return { success: true, @@ -596,7 +365,7 @@ export default class MathOperator extends MathComponent { setDependency: "mathNumberChildren", desiredValue, childIndex, - variableIndex, + variableIndex: 0, }, ], }; @@ -613,7 +382,7 @@ export default class MathOperator extends MathComponent { }; // create new version on canBeModified that is true only if - // there is just one child or child list component that can be modified + // there is just one child component that can be modified // and we have a inverseMathOperator/inverseNumberOperator stateVariableDefinitions.canBeModified = { returnDependencies: () => ({ @@ -630,11 +399,6 @@ export default class MathOperator extends MathComponent { childGroups: ["maths", "numbers"], variableNames: ["canBeModified"], }, - mathNumberListChildren: { - dependencyType: "child", - childGroups: ["mathLists", "numberLists"], - variableNames: ["numComponents"], - }, isNumericOperator: { dependencyType: "stateVariable", variableName: "isNumericOperator", @@ -662,19 +426,11 @@ export default class MathOperator extends MathComponent { // TODO: if there are no children, canBeModified may be incorrectly set to true // But, we include this exception so that canBeModified is not set to false // in macros, where children aren't copied - if ( - dependencyValues.mathNumberChildren.length + - dependencyValues.mathNumberListChildren.length > - 0 - ) { + if (dependencyValues.mathNumberChildren.length > 0) { let nModifiable = dependencyValues.mathNumberChildren.filter( (x) => x.stateValues.canBeModified, - ).length + - dependencyValues.mathNumberListChildren.reduce( - (a, c) => a + c.stateValues.numComponents, - 0, - ); + ).length; if (nModifiable !== 1) { canBeModified = false; diff --git a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts index e8f198841..14dc429c4 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts @@ -1745,6 +1745,35 @@ describe("Answer tag tests", async () => { }); }); + it("answer from numberList", async () => { + const doenetML = ` + + + $mi1 $mi2 = 1 2 + + + `; + + await test_answer_multiple_inputs({ + doenetML, + answers: [ + { values: ["1", "2"], credit: 1 }, + { values: ["3", "2"], credit: 0.5 }, + { values: ["3", ""], credit: 0 }, + { values: ["2", ""], credit: 0.5 }, + { values: ["", "2"], credit: 0.5 }, + { values: ["", "3"], credit: 0 }, + { values: ["", "1"], credit: 0.5 }, + { values: ["1", ""], credit: 0.5 }, + { values: ["2", "1"], credit: 0.5 }, + ], + inputs: [ + { type: "math", name: "/mi1" }, + { type: "math", name: "/mi2" }, + ], + }); + }); + it("answer award with text", async () => { const doenetML = ` hello there @@ -1843,7 +1872,7 @@ describe("Answer tag tests", async () => { }); }); - it("answer award with text, initally unresolved", async () => { + it("answer award with text, initially unresolved", async () => { const doenetML = ` $n diff --git a/packages/doenetml-worker/src/test/tagSpecific/copy.test.ts b/packages/doenetml-worker/src/test/tagSpecific/copy.test.ts index 8bc6da005..8d9686271 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/copy.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/copy.test.ts @@ -1391,7 +1391,7 @@ describe("Copy tag tests", async () => { // move A2 A2 = [5, 4]; - core.requestAction({ + await core.requestAction({ actionName: "movePoint", componentName: "/A2", args: { x: A2[0], y: A2[1] }, diff --git a/packages/doenetml-worker/src/test/tagSpecific/mathoperators.test.ts b/packages/doenetml-worker/src/test/tagSpecific/mathoperators.test.ts index dbaf4120f..8d8955f63 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/mathoperators.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/mathoperators.test.ts @@ -540,7 +540,7 @@ describe("Math operator tests", async () => { stateVariables["/numberComponentsCommas"].stateValues.value.tree, ).eqls(["apply", "sum", ["tuple", 3, 17, 1]]); expect( - await await stateVariables["/numberComponentsCommas"].stateValues + await stateVariables["/numberComponentsCommas"].stateValues .isNumber, ).eq(false); expect( diff --git a/packages/doenetml-worker/src/test/tagSpecific/polygon.test.ts b/packages/doenetml-worker/src/test/tagSpecific/polygon.test.ts index 5ecfa9c07..f8781583a 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/polygon.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/polygon.test.ts @@ -4447,7 +4447,7 @@ describe("Polygon tag tests", async () => { // cannot move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4471,7 +4471,7 @@ describe("Polygon tag tests", async () => { // cannot move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4507,7 +4507,7 @@ describe("Polygon tag tests", async () => { // can move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4531,7 +4531,7 @@ describe("Polygon tag tests", async () => { // cannot move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4567,7 +4567,7 @@ describe("Polygon tag tests", async () => { // can move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4591,7 +4591,7 @@ describe("Polygon tag tests", async () => { // can move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4627,7 +4627,7 @@ describe("Polygon tag tests", async () => { // cannot move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -4651,7 +4651,7 @@ describe("Polygon tag tests", async () => { // can move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/p", args: { @@ -6065,7 +6065,7 @@ describe("Polygon tag tests", async () => { ]); } - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/g2/pg", args: { @@ -6214,7 +6214,7 @@ describe("Polygon tag tests", async () => { ]); } - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/g2/pg", args: { @@ -6327,7 +6327,7 @@ describe("Polygon tag tests", async () => { 0.5 * (vertices[1][0] - centroid[0]) + centroid[1], ]; - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/g1/pg", args: { @@ -6351,7 +6351,7 @@ describe("Polygon tag tests", async () => { ]); } - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/g2/pg", args: { @@ -6378,7 +6378,7 @@ describe("Polygon tag tests", async () => { -2 * (vertices[2][1] - centroid[1]) + centroid[1], ]; - await await core.requestAction({ + await core.requestAction({ actionName: "movePolygon", componentName: "/g3/pg", args: { @@ -6405,7 +6405,7 @@ describe("Polygon tag tests", async () => { -0.25 * (vertices[3][0] - centroid[0]) + centroid[1], ]; - await await core.requestAction({ + await core.requestAction({ actionName: "movePoint", componentName: "/g2/v4", args: { x: requested_vertex_3[0], y: requested_vertex_3[1] }, @@ -6675,7 +6675,7 @@ describe("Polygon tag tests", async () => { // change rotation point, then moving single copied vertex gets rotation and dilation - await await core.requestAction({ + await core.requestAction({ actionName: "movePoint", componentName: "/g1/rotationPoint", args: { x: 6, y: -2 }, diff --git a/packages/doenetml-worker/src/test/tagSpecific/polyline.test.ts b/packages/doenetml-worker/src/test/tagSpecific/polyline.test.ts index f027389f9..413dcf9d9 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/polyline.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/polyline.test.ts @@ -3884,7 +3884,7 @@ describe("Polyline tag tests", async () => { // cannot move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -3908,7 +3908,7 @@ describe("Polyline tag tests", async () => { // cannot move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -3944,7 +3944,7 @@ describe("Polyline tag tests", async () => { // can move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -3968,7 +3968,7 @@ describe("Polyline tag tests", async () => { // cannot move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -4004,7 +4004,7 @@ describe("Polyline tag tests", async () => { // can move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -4028,7 +4028,7 @@ describe("Polyline tag tests", async () => { // can move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -4064,7 +4064,7 @@ describe("Polyline tag tests", async () => { // cannot move single vertex - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -4088,7 +4088,7 @@ describe("Polyline tag tests", async () => { // can move all vertices - await await core.requestAction({ + await core.requestAction({ actionName: "movePolyline", componentName: "/p", args: { @@ -4502,7 +4502,7 @@ describe("Polyline tag tests", async () => { $vertexInput - + $(../g1/pg{name="pg"}) diff --git a/packages/doenetml-worker/src/test/tagSpecific/selectsamplerandomnumbers.test.ts b/packages/doenetml-worker/src/test/tagSpecific/selectsamplerandomnumbers.test.ts index de726755b..adb78af25 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/selectsamplerandomnumbers.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/selectsamplerandomnumbers.test.ts @@ -837,7 +837,7 @@ describe("SelectRandomNumbers and SampleRandomNumbers tag tests", async () => { expect(me.math.mean(sample2numbersc)).closeTo(0, 6); expect(me.math.variance(sample2numbersc, "uncorrected")).closeTo( 18 ** 2, - 120, + 150, ); for (let ind = 0; ind < 10; ind++) { @@ -1743,8 +1743,8 @@ describe("SelectRandomNumbers and SampleRandomNumbers tag tests", async () => { specifiedTo, specifiedStep, sampleComponent: stateVariables["/samples"], - allowedErrorInMean: 1, - allowedErrorInVariance: 3, + allowedErrorInMean: 1.5, + allowedErrorInVariance: 4, checkAllSamples: true, stateVariables, }); diff --git a/packages/doenetml-worker/src/utils/rounding.js b/packages/doenetml-worker/src/utils/rounding.js index 6ed03bca7..1ebcae751 100644 --- a/packages/doenetml-worker/src/utils/rounding.js +++ b/packages/doenetml-worker/src/utils/rounding.js @@ -1,7 +1,6 @@ export function returnRoundingStateVariableDefinitions({ - childsGroupIfSingleMatch = [], + childGroupsIfSingleMatch = [], childGroupsToStopSingleMatch = [], - includeListParents = false, additionalAttributeComponent = null, displayDigitsDefault = 3, displaySmallAsZeroDefault = 1e-14, @@ -17,16 +16,14 @@ export function returnRoundingStateVariableDefinitions({ defaultValue: displayDigitsDefault, returnDependencies: roundingDependencies({ stateVariable: "displayDigits", - childsGroupIfSingleMatch, + childGroupsIfSingleMatch, childGroupsToStopSingleMatch, ignoreShadowsIfHaveAttribute: "displayDecimals", - includeListParents, additionalAttributeComponent, }), definition: roundingDefinition({ stateVariable: "displayDigits", valueIfIgnore: 0, - includeListParents, }), }; @@ -39,16 +36,14 @@ export function returnRoundingStateVariableDefinitions({ defaultValue: 2, returnDependencies: roundingDependencies({ stateVariable: "displayDecimals", - childsGroupIfSingleMatch, + childGroupsIfSingleMatch, childGroupsToStopSingleMatch, ignoreShadowsIfHaveAttribute: "displayDigits", - includeListParents, additionalAttributeComponent, }), definition: roundingDefinition({ stateVariable: "displayDecimals", valueIfIgnore: -Infinity, - includeListParents, }), }; @@ -61,14 +56,12 @@ export function returnRoundingStateVariableDefinitions({ defaultValue: displaySmallAsZeroDefault, returnDependencies: roundingDependencies({ stateVariable: "displaySmallAsZero", - childsGroupIfSingleMatch, + childGroupsIfSingleMatch, childGroupsToStopSingleMatch, - includeListParents, additionalAttributeComponent, }), definition: roundingDefinition({ stateVariable: "displaySmallAsZero", - includeListParents, }), }; @@ -81,14 +74,12 @@ export function returnRoundingStateVariableDefinitions({ defaultValue: false, returnDependencies: roundingDependencies({ stateVariable: "padZeros", - childsGroupIfSingleMatch, + childGroupsIfSingleMatch, childGroupsToStopSingleMatch, - includeListParents, additionalAttributeComponent, }), definition: roundingDefinition({ stateVariable: "padZeros", - includeListParents, }), }; @@ -97,10 +88,9 @@ export function returnRoundingStateVariableDefinitions({ function roundingDependencies({ stateVariable, - childsGroupIfSingleMatch, + childGroupsIfSingleMatch, childGroupsToStopSingleMatch, ignoreShadowsIfHaveAttribute = null, - includeListParents = false, additionalAttributeComponent = null, }) { return function () { @@ -112,7 +102,7 @@ function roundingDependencies({ }, singleMatchChildren: { dependencyType: "child", - childGroups: childsGroupIfSingleMatch, + childGroups: childGroupsIfSingleMatch, variableNames: [stateVariable], variablesOptional: true, }, @@ -122,19 +112,6 @@ function roundingDependencies({ }, }; - if (includeListParents) { - dependencies.fromMathListParent = { - dependencyType: "parentStateVariable", - parentComponentType: "mathList", - variableName: stateVariable, - }; - dependencies.fromNumberListParent = { - dependencyType: "parentStateVariable", - parentComponentType: "numberList", - variableName: stateVariable, - }; - } - if (ignoreShadowsIfHaveAttribute) { dependencies.attributePromptingIgnore = { dependencyType: "attributeComponent", @@ -158,32 +135,8 @@ function roundingDependencies({ }; } -function roundingDefinition({ - stateVariable, - valueIfIgnore = null, - includeListParents, -}) { +function roundingDefinition({ stateVariable, valueIfIgnore = null }) { return function ({ dependencyValues, usedDefault }) { - if (includeListParents) { - if (dependencyValues.fromMathListParent !== null) { - // A mathlist parent overrides everything else. - return { - setValue: { - [stateVariable]: dependencyValues.fromMathListParent, - }, - }; - } - - if (dependencyValues.fromNumberListParent !== null) { - // A numberlist parent overrides everything else. - return { - setValue: { - [stateVariable]: dependencyValues.fromNumberListParent, - }, - }; - } - } - let foundDefaultValue = false; let theDefaultValueFound; diff --git a/packages/doenetml/src/Viewer/renderers/mathList.jsx b/packages/doenetml/src/Viewer/renderers/mathList.jsx deleted file mode 100644 index c84354488..000000000 --- a/packages/doenetml/src/Viewer/renderers/mathList.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { MathJax } from "better-react-mathjax"; -import React from "react"; -import useDoenetRenderer from "../useDoenetRenderer"; - -export default React.memo(function MathList(props) { - let { name, id, SVs, children } = useDoenetRenderer(props); - - if (SVs.hidden) { - return null; - } - - if (children.length === 0 && SVs.latex) { - return ( - - - - - {"\\(" + SVs.latex + "\\)"} - - - - ); - } - - if (children.length === 0) { - return ; - } - - let withCommas = children - .slice(1) - .reduce((a, b) => [...a, ", ", b], [children[0]]); - - return ( - - - {withCommas} - - ); -}); diff --git a/packages/doenetml/src/Viewer/renderers/numberList.jsx b/packages/doenetml/src/Viewer/renderers/numberList.jsx deleted file mode 100644 index 2c12569af..000000000 --- a/packages/doenetml/src/Viewer/renderers/numberList.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { MathJax } from "better-react-mathjax"; -import React from "react"; -import useDoenetRenderer from "../useDoenetRenderer"; - -export default React.memo(function MathList(props) { - let { name, id, SVs, children } = useDoenetRenderer(props); - - if (SVs.hidden) { - return null; - } - - if (children.length === 0 && SVs.text) { - return ( - - - {SVs.text} - - ); - } - - if (children.length === 0) { - return ; - } - - let withCommas = children - .slice(1) - .reduce((a, b) => [...a, ", ", b], [children[0]]); - - return ( - - - {withCommas} - - ); -}); From 305547be4692a8e9c784642749163658a8943816 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Fri, 25 Oct 2024 16:13:16 -0500 Subject: [PATCH 02/13] correctly shadow composites that shadow prop variables --- packages/doenetml-worker/src/Core.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/doenetml-worker/src/Core.js b/packages/doenetml-worker/src/Core.js index 305f5998f..d7d9f2cbf 100644 --- a/packages/doenetml-worker/src/Core.js +++ b/packages/doenetml-worker/src/Core.js @@ -1872,6 +1872,7 @@ export default class Core { mediatingShadowComposite.mediatesShadows.push({ shadowing: newComponent.componentName, shadowed: name, + propVariable: dep.propVariable, }); if (dep.isPrimaryShadow) { @@ -2856,9 +2857,9 @@ export default class Core { // mediates the shadow of compositeMediatingTheShadow let foundCircular = false; - let shadowedByShadowed = shadowedComposite.mediatesShadows?.map( - (v) => v.shadowed, - ); + let shadowedByShadowed = shadowedComposite.mediatesShadows + ?.filter((v) => v.propVariable === undefined) + .map((v) => v.shadowed); while (shadowedByShadowed?.length > 0) { if ( @@ -2893,7 +2894,9 @@ export default class Core { if (comp.mediatesShadows) { return [ ...acc, - ...comp.mediatesShadows.map((v) => v.shadowed), + ...comp.mediatesShadows + .filter((v) => v.propVariable === undefined) + .map((v) => v.shadowed), ]; } else { return acc; @@ -4180,7 +4183,6 @@ export default class Core { componentClass, }) { let targetComponent = this._components[redefineDependencies.targetName]; - let core = this; if (redefineDependencies.propVariable) { // if we have an array entry state variable that hasn't been created yet @@ -9448,7 +9450,7 @@ export default class Core { let addedComponents = {}; let parentsOfDeleted = new Set(); - if (component.shadows) { + if (component.shadows && !component.shadows.propVariable) { // if shadows, don't update replacements // instead, replacements will get updated when shadowed component // is updated From d090fe44f018fc72000835819e55bd46e086b435 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 28 Oct 2024 11:44:53 -0500 Subject: [PATCH 03/13] convert textList and booleanList components to composites --- package-lock.json | 8 +- package.json | 2 +- .../doenetml-worker/src/components/Answer.js | 29 +- .../doenetml-worker/src/components/Award.js | 261 ++--- .../doenetml-worker/src/components/Boolean.js | 188 +-- .../src/components/BooleanList.js | 492 ++++---- .../doenetml-worker/src/components/Math.js | 1014 +---------------- .../src/components/MathInput.js | 6 + .../src/components/MathList.js | 29 +- .../doenetml-worker/src/components/Number.js | 30 + .../src/components/NumberList.js | 31 +- .../doenetml-worker/src/components/Text.js | 56 +- .../src/components/TextInput.js | 4 + .../src/components/TextList.js | 488 ++++---- .../doenetml-worker/src/components/When.js | 16 - .../src/test/tagSpecific/answer.test.ts | 181 ++- .../src/test/tagSpecific/boolean.test.ts | 219 +++- .../src/test/tagSpecific/graph.test.ts | 26 + .../src/test/tagSpecific/mathinput.test.ts | 264 +++++ .../src/test/tagSpecific/text.test.ts | 74 +- .../src/test/tagSpecific/textinput.test.ts | 88 ++ .../doenetml-worker/src/utils/booleanLogic.js | 623 +++++----- .../utils/mathVectorMatrixStateVariables.ts | 676 +++++++++++ .../doenetml-worker/src/utils/parseMath.ts | 375 ++++++ packages/doenetml-worker/src/utils/text.ts | 220 +++- .../src/Viewer/renderers/textList.jsx | 30 - packages/utils/src/math/subset-of-reals.js | 5 + 27 files changed, 3250 insertions(+), 2185 deletions(-) create mode 100644 packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts create mode 100644 packages/doenetml-worker/src/utils/parseMath.ts delete mode 100644 packages/doenetml/src/Viewer/renderers/textList.jsx diff --git a/package-lock.json b/package-lock.json index 10176fe6d..02c69e597 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "./packages/*" ], "dependencies": { - "math-expressions": "^2.0.0-alpha70", + "math-expressions": "^2.0.0-alpha71", "react-router-dom": "^6.26.2" }, "devDependencies": { @@ -13748,9 +13748,9 @@ } }, "node_modules/math-expressions": { - "version": "2.0.0-alpha70", - "resolved": "https://registry.npmjs.org/math-expressions/-/math-expressions-2.0.0-alpha70.tgz", - "integrity": "sha512-hKdOeRjcO2EkTpVw5j3gt6g89QW0j9v+ke0griDaZPYN3l2HMJowk4U5dARVJoEpX4nfN9jIpSnbUA/b9UQ5QA==", + "version": "2.0.0-alpha71", + "resolved": "https://registry.npmjs.org/math-expressions/-/math-expressions-2.0.0-alpha71.tgz", + "integrity": "sha512-NMTjBbuzchbEBId1VDP0VzB3qNiqu+sJHS34MyH5ONQtJFzxqXzKH7nqsvemdsTpnlFWMjFzHEPEa524E9oo2A==", "license": "(GPL-3.0 OR Apache-2.0)", "dependencies": { "@babel/cli": "^7.25.7", diff --git a/package.json b/package.json index be497ce7d..30c47a8a6 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "tabWidth": 4 }, "dependencies": { - "math-expressions": "^2.0.0-alpha70", + "math-expressions": "^2.0.0-alpha71", "react-router-dom": "^6.26.2" } } diff --git a/packages/doenetml-worker/src/components/Answer.js b/packages/doenetml-worker/src/components/Answer.js index c2b744257..21159329e 100644 --- a/packages/doenetml-worker/src/components/Answer.js +++ b/packages/doenetml-worker/src/components/Answer.js @@ -167,6 +167,8 @@ export default class Answer extends InlineComponent { }; attributes.type = { createPrimitiveOfType: "string", + createStateVariable: "type", + defaultValue: null, }; attributes.disableAfterCorrect = { @@ -251,8 +253,8 @@ export default class Answer extends InlineComponent { componentAttributes, componentInfoObjects, }) { - // if chidren are strings and macros - // wrap with award and type + // if children are strings and macros + // wrap with award function checkForResponseDescendant(components) { for (let component of components) { @@ -596,28 +598,7 @@ export default class Answer extends InlineComponent { ...childrenToNotWrapEnd, ]; } else { - // if have one child and it has a specified componentType - // then no need to wrap with componentType - - let needToWrapWithComponentType = - childrenToWrap.length > 1 || - (componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: childrenToWrap[0].componentType, - baseComponentType: "_composite", - }) && - !childrenToWrap[0].props?.componentType); - - let awardChildren; - if (needToWrapWithComponentType) { - awardChildren = [ - { - componentType: type, - children: childrenToWrap, - }, - ]; - } else { - awardChildren = childrenToWrap; - } + let awardChildren = childrenToWrap; newChildren = [ ...childrenToNotWrapBegin, { diff --git a/packages/doenetml-worker/src/components/Award.js b/packages/doenetml-worker/src/components/Award.js index 8cf832860..19376267c 100644 --- a/packages/doenetml-worker/src/components/Award.js +++ b/packages/doenetml-worker/src/components/Award.js @@ -1,6 +1,10 @@ import BaseComponent from "./abstract/BaseComponent"; import me from "math-expressions"; -import { evaluateLogic } from "../utils/booleanLogic"; +import { + buildParsedExpression, + evaluateLogic, + returnChildrenByCodeStateVariableDefinitions, +} from "../utils/booleanLogic"; import { getNamespaceFromName } from "@doenet/utils"; export default class Award extends BaseComponent { @@ -317,40 +321,18 @@ export default class Award extends BaseComponent { componentTypes: ["when"], }, { - group: "maths", - componentTypes: ["math"], + group: "strings", + componentTypes: ["string"], }, { - group: "numbers", - componentTypes: ["number"], - }, - { - group: "texts", - componentTypes: ["text"], - }, - { - group: "booleans", - componentTypes: ["boolean"], - }, - { - group: "mathLists", - componentTypes: ["mathList"], - }, - { - group: "numberLists", - componentTypes: ["numberList"], - }, - { - group: "textLists", - componentTypes: ["textList"], - }, - { - group: "booleanLists", - componentTypes: ["booleanList"], - }, - { - group: "otherComparableTypes", - componentTypes: ["orbitalDiagram"], + group: "comparableTypes", + componentTypes: [ + "math", + "number", + "text", + "boolean", + "orbitalDiagram", + ], }, ]; } @@ -359,44 +341,84 @@ export default class Award extends BaseComponent { let stateVariableDefinitions = super.returnStateVariableDefinitions(); stateVariableDefinitions.parsedExpression = { - additionalStateVariablesDefined: ["requireInputInAnswer"], + additionalStateVariablesDefined: [ + "requireInputInAnswer", + "codePre", + ], returnDependencies: () => ({ whenChild: { dependencyType: "child", childGroups: ["whens"], }, - typeChildren: { + // call it "allChildren" so that can use the buildParsedExpression function that Boolean uses + allChildren: { dependencyType: "child", - childGroups: [ - "maths", - "numbers", - "texts", - "booleans", - "mathLists", - "numberLists", - "textLists", - "booleanLists", - "otherComparableTypes", - ], + childGroups: ["strings", "comparableTypes"], + }, + stringChildren: { + dependencyType: "child", + childGroups: ["strings"], + variableNames: ["value"], + }, + answerType: { + dependencyType: "parentStateVariable", + parentComponentType: "answer", + variableName: "type", + }, + splitSymbols: { + dependencyType: "stateVariable", + variableName: "splitSymbols", }, }), - definition: function ({ dependencyValues }) { + definition: function ({ dependencyValues, componentInfoObjects }) { let parsedExpression = null; let requireInputInAnswer = false; + let codePre = ""; if ( dependencyValues.whenChild.length == 0 && - dependencyValues.typeChildren.length > 0 + dependencyValues.allChildren.length > 0 ) { requireInputInAnswer = true; - parsedExpression = me.fromAst(["=", "comp1", "comp2"]); + let doNotSplit = + dependencyValues.splitSymbols === false || + (dependencyValues.answerType && + !["number", "math"].includes( + dependencyValues.answerType, + )); + + let { setValue } = buildParsedExpression({ + dependencyValues, + componentInfoObjects, + doNotSplit, + splitAtInitialLevel: true, + }); + + codePre = setValue.codePre; + + parsedExpression = me.fromAst([ + "=", + codePre + "Input", + setValue.parsedExpression.tree, + ]); } - return { setValue: { parsedExpression, requireInputInAnswer } }; + return { + setValue: { + parsedExpression, + requireInputInAnswer, + codePre, + }, + }; }, }; + Object.assign( + stateVariableDefinitions, + returnChildrenByCodeStateVariableDefinitions(), + ); + stateVariableDefinitions.creditAchievedIfSubmit = { public: true, shadowingInstructions: { @@ -421,63 +443,10 @@ export default class Award extends BaseComponent { childGroups: ["whens"], variableNames: ["fractionSatisfied"], }, - mathChild: { - dependencyType: "child", - childGroups: ["maths"], - variableNames: ["value", "unordered"], - }, - numberChild: { - dependencyType: "child", - childGroups: ["numbers"], - variableNames: ["value"], - }, - textChild: { - dependencyType: "child", - childGroups: ["texts"], - variableNames: ["value"], - }, - booleanChild: { - dependencyType: "child", - childGroups: ["booleans"], - variableNames: ["value"], - }, - mathListChild: { - dependencyType: "child", - childGroups: ["mathLists"], - variableNames: ["maths", "unordered"], - }, - numberListChild: { - dependencyType: "child", - childGroups: ["numberLists"], - variableNames: ["numbers", "unordered"], - }, - textListChild: { - dependencyType: "child", - childGroups: ["textLists"], - variableNames: ["texts", "unordered"], - }, - booleanListChild: { - dependencyType: "child", - childGroups: ["booleanLists"], - variableNames: ["booleans", "unordered"], - }, - otherComparableChild: { - dependencyType: "child", - childGroups: ["otherComparableTypes"], - variableNames: ["value"], - }, answerInput: { dependencyType: "parentStateVariable", variableName: "inputChildWithValues", }, - parsedExpression: { - dependencyType: "stateVariable", - variableName: "parsedExpression", - }, - matchPartial: { - dependencyType: "stateVariable", - variableName: "matchPartial", - }, symbolicEquality: { dependencyType: "stateVariable", variableName: "symbolicEquality", @@ -526,6 +495,44 @@ export default class Award extends BaseComponent { dependencyType: "stateVariable", variableName: "matchBlanks", }, + parsedExpression: { + dependencyType: "stateVariable", + variableName: "parsedExpression", + }, + allChildren: { + dependencyType: "child", + childGroups: ["strings", "comparableTypes"], + variableNames: ["value"], + variablesOptional: true, + }, + booleanChildrenByCode: { + dependencyType: "stateVariable", + variableName: "booleanChildrenByCode", + }, + textChildrenByCode: { + dependencyType: "stateVariable", + variableName: "textChildrenByCode", + }, + mathChildrenByCode: { + dependencyType: "stateVariable", + variableName: "mathChildrenByCode", + }, + numberChildrenByCode: { + dependencyType: "stateVariable", + variableName: "numberChildrenByCode", + }, + otherChildrenByCode: { + dependencyType: "stateVariable", + variableName: "otherChildrenByCode", + }, + matchPartial: { + dependencyType: "stateVariable", + variableName: "matchPartial", + }, + codePre: { + dependencyType: "stateVariable", + variableName: "codePre", + }, }), definition: function ({ dependencyValues, usedDefault }) { let fractionSatisfiedIfSubmit; @@ -803,51 +810,12 @@ export default class Award extends BaseComponent { } function evaluateLogicDirectlyFromChildren({ dependencyValues, usedDefault }) { - let dependenciesForEvaluateLogic = { - mathChildrenByCode: {}, - mathListChildrenByCode: {}, - numberChildrenByCode: {}, - numberListChildrenByCode: {}, - textChildrenByCode: {}, - textListChildrenByCode: {}, - booleanChildrenByCode: {}, - booleanListChildrenByCode: {}, - otherChildrenByCode: {}, - }; + let dependenciesForEvaluateLogic = {}; Object.assign(dependenciesForEvaluateLogic, dependencyValues); let canOverrideUnorderedCompare = usedDefault.unorderedCompare; - if (dependencyValues.textChild.length > 0) { - dependenciesForEvaluateLogic.textChildrenByCode.comp2 = - dependencyValues.textChild[0]; - } else if (dependencyValues.mathChild.length > 0) { - dependenciesForEvaluateLogic.mathChildrenByCode.comp2 = - dependencyValues.mathChild[0]; - } else if (dependencyValues.numberChild.length > 0) { - dependenciesForEvaluateLogic.numberChildrenByCode.comp2 = - dependencyValues.numberChild[0]; - } else if (dependencyValues.booleanChild.length > 0) { - dependenciesForEvaluateLogic.booleanChildrenByCode.comp2 = - dependencyValues.booleanChild[0]; - } else if (dependencyValues.textListChild.length > 0) { - dependenciesForEvaluateLogic.textListChildrenByCode.comp2 = - dependencyValues.textListChild[0]; - } else if (dependencyValues.mathListChild.length > 0) { - dependenciesForEvaluateLogic.mathListChildrenByCode.comp2 = - dependencyValues.mathListChild[0]; - } else if (dependencyValues.numberListChild.length > 0) { - dependenciesForEvaluateLogic.numberListChildrenByCode.comp2 = - dependencyValues.numberListChild[0]; - } else if (dependencyValues.booleanListChild.length > 0) { - dependenciesForEvaluateLogic.booleanListChildrenByCode.comp2 = - dependencyValues.booleanListChild[0]; - } else if (dependencyValues.otherComparableChild.length > 0) { - dependenciesForEvaluateLogic.otherChildrenByCode.comp2 = - dependencyValues.otherComparableChild[0]; - } - let answerValue = dependencyValues.answerInput.stateValues.immediateValue; if (answerValue === undefined) { answerValue = dependencyValues.answerInput.stateValues.value; @@ -857,14 +825,15 @@ function evaluateLogicDirectlyFromChildren({ dependencyValues, usedDefault }) { stateValues: { value: answerValue }, }; + let inputCode = dependencyValues.codePre + "Input"; if (dependencyValues.answerInput.componentType === "textInput") { - dependenciesForEvaluateLogic.textChildrenByCode.comp1 = + dependenciesForEvaluateLogic.textChildrenByCode[inputCode] = answerChildForLogic; } else if (dependencyValues.answerInput.componentType === "booleanInput") { - dependenciesForEvaluateLogic.booleanChildrenByCode.comp1 = + dependenciesForEvaluateLogic.booleanChildrenByCode[inputCode] = answerChildForLogic; } else { - dependenciesForEvaluateLogic.mathChildrenByCode.comp1 = + dependenciesForEvaluateLogic.mathChildrenByCode[inputCode] = answerChildForLogic; } diff --git a/packages/doenetml-worker/src/components/Boolean.js b/packages/doenetml-worker/src/components/Boolean.js index eb96f3d85..77dcc9ddf 100644 --- a/packages/doenetml-worker/src/components/Boolean.js +++ b/packages/doenetml-worker/src/components/Boolean.js @@ -1,5 +1,9 @@ import InlineComponent from "./abstract/InlineComponent"; -import { evaluateLogic, buildParsedExpression } from "../utils/booleanLogic"; +import { + evaluateLogic, + buildParsedExpression, + returnChildrenByCodeStateVariableDefinitions, +} from "../utils/booleanLogic"; export default class BooleanComponent extends InlineComponent { static componentType = "boolean"; @@ -100,13 +104,9 @@ export default class BooleanComponent extends InlineComponent { group: "comparableTypes", componentTypes: [ "math", - "mathList", "number", - "numberList", "text", - "textList", "boolean", - "booleanList", "orbitalDiagram", ], }, @@ -116,6 +116,36 @@ export default class BooleanComponent extends InlineComponent { static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); + stateVariableDefinitions.inUnorderedList = { + defaultValue: false, + returnDependencies: () => ({ + sourceCompositeUnordered: { + dependencyType: "sourceCompositeStateVariable", + variableName: "unordered", + }, + }), + definition({ dependencyValues, usedDefault }) { + if ( + dependencyValues.sourceCompositeUnordered !== null && + !usedDefault.sourceCompositeUnordered + ) { + return { + setValue: { + inUnorderedList: Boolean( + dependencyValues.sourceCompositeUnordered, + ), + }, + }; + } else { + return { + setValue: { + inUnorderedList: false, + }, + }; + } + }, + }; + stateVariableDefinitions.parsedExpression = { additionalStateVariablesDefined: ["codePre"], returnDependencies: () => ({ @@ -132,134 +162,10 @@ export default class BooleanComponent extends InlineComponent { definition: buildParsedExpression, }; - stateVariableDefinitions.mathChildrenByCode = { - additionalStateVariablesDefined: [ - "mathListChildrenByCode", - "numberChildrenByCode", - "numberListChildrenByCode", - "textChildrenByCode", - "textListChildrenByCode", - "booleanChildrenByCode", - "booleanListChildrenByCode", - "otherChildrenByCode", - ], - returnDependencies: () => ({ - allChildren: { - dependencyType: "child", - childGroups: ["strings", "comparableTypes"], - variableNames: [ - "value", - "texts", - "maths", - "numbers", - "booleans", - "fractionSatisfied", - "unordered", - ], - variablesOptional: true, - }, - codePre: { - dependencyType: "stateVariable", - variableName: "codePre", - }, - }), - definition({ dependencyValues, componentInfoObjects }) { - let mathChildrenByCode = {}; - let mathListChildrenByCode = {}; - let numberChildrenByCode = {}; - let numberListChildrenByCode = {}; - let textChildrenByCode = {}; - let textListChildrenByCode = {}; - let booleanChildrenByCode = {}; - let booleanListChildrenByCode = {}; - let otherChildrenByCode = {}; - let subnum = 0; - - let codePre = dependencyValues.codePre; - - for (let child of dependencyValues.allChildren) { - if (typeof child !== "string") { - // a math, mathList, text, textList, boolean, or booleanList - let code = codePre + subnum; - - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "math", - }) - ) { - mathChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) - ) { - mathListChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "number", - }) - ) { - numberChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", - }) - ) { - numberListChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "text", - }) - ) { - textChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "textList", - }) - ) { - textListChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "boolean", - }) - ) { - booleanChildrenByCode[code] = child; - } else if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "booleanList", - }) - ) { - booleanListChildrenByCode[code] = child; - } else { - otherChildrenByCode[code] = child; - } - subnum += 1; - } - } - - return { - setValue: { - mathChildrenByCode, - mathListChildrenByCode, - numberChildrenByCode, - numberListChildrenByCode, - textChildrenByCode, - textListChildrenByCode, - booleanChildrenByCode, - booleanListChildrenByCode, - otherChildrenByCode, - }, - }; - }, - }; + Object.assign( + stateVariableDefinitions, + returnChildrenByCodeStateVariableDefinitions(), + ); stateVariableDefinitions.value = { public: true, @@ -334,34 +240,18 @@ export default class BooleanComponent extends InlineComponent { dependencyType: "stateVariable", variableName: "booleanChildrenByCode", }, - booleanListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "booleanListChildrenByCode", - }, textChildrenByCode: { dependencyType: "stateVariable", variableName: "textChildrenByCode", }, - textListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "textListChildrenByCode", - }, mathChildrenByCode: { dependencyType: "stateVariable", variableName: "mathChildrenByCode", }, - mathListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "mathListChildrenByCode", - }, numberChildrenByCode: { dependencyType: "stateVariable", variableName: "numberChildrenByCode", }, - numberListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "numberListChildrenByCode", - }, otherChildrenByCode: { dependencyType: "stateVariable", variableName: "otherChildrenByCode", diff --git a/packages/doenetml-worker/src/components/BooleanList.js b/packages/doenetml-worker/src/components/BooleanList.js index 571381dfd..0e2dc8b1f 100644 --- a/packages/doenetml-worker/src/components/BooleanList.js +++ b/packages/doenetml-worker/src/components/BooleanList.js @@ -1,10 +1,16 @@ -import InlineComponent from "./abstract/InlineComponent"; +import CompositeComponent from "./abstract/CompositeComponent"; import { returnGroupIntoComponentTypeSeparatedBySpacesOutsideParens } from "./commonsugar/lists"; +import { + convertAttributesForComponentType, + postProcessCopy, +} from "../utils/copy"; +import { processAssignNames } from "../utils/naming"; -export default class BooleanList extends InlineComponent { +export default class BooleanList extends CompositeComponent { static componentType = "booleanList"; - static rendererType = "asList"; - static renderChildren = true; + + static stateVariableToEvaluateAfterReplacements = + "readyToExpandWhenResolved"; static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -12,6 +18,7 @@ export default class BooleanList extends InlineComponent { // when another component has a attribute that is a booleanList, // use the booleans state variable to populate that attribute static stateVariableToBeShadowed = "booleans"; + static primaryStateVariableForDefinition = "booleansShadow"; // even if inside a component that turned on descendantCompositesMustHaveAReplacement // don't required composite replacements @@ -28,14 +35,23 @@ export default class BooleanList extends InlineComponent { attributes.maxNumber = { createComponentOfType: "number", createStateVariable: "maxNumber", - defaultValue: null, + defaultValue: Infinity, public: true, }; + + attributes.fixed = { + leaveRaw: true, + }; + + attributes.isResponse = { + leaveRaw: true, + }; + return attributes; } // Include children that can be added due to sugar - static additionalSchemaChildren = ["math", "number", "string"]; + static additionalSchemaChildren = ["string"]; static returnSugarInstructions() { let sugarInstructions = super.returnSugarInstructions(); @@ -59,22 +75,28 @@ export default class BooleanList extends InlineComponent { group: "booleans", componentTypes: ["boolean"], }, - { - group: "booleanLists", - componentTypes: ["booleanList"], - }, ]; } static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); - // set overrideChildHide so that children are hidden - // only based on whether or not the list is hidden - // so that can't have a list with partially hidden components - stateVariableDefinitions.overrideChildHide = { + stateVariableDefinitions.booleansShadow = { + defaultValue: null, + hasEssential: true, + returnDependencies: () => ({}), + definition: () => ({ + useEssentialOrDefaultValue: { + booleansShadow: true, + }, + }), + }; + + stateVariableDefinitions.asList = { returnDependencies: () => ({}), - definition: () => ({ setValue: { overrideChildHide: true } }), + definition() { + return { setValue: { asList: true } }; + }, }; stateVariableDefinitions.numComponents = { @@ -82,85 +104,59 @@ export default class BooleanList extends InlineComponent { shadowingInstructions: { createComponentOfType: "number", }, - additionalStateVariablesDefined: ["childIndexByArrayKey"], + additionalStateVariablesDefined: ["childNameByComponent"], returnDependencies() { return { maxNumber: { dependencyType: "stateVariable", variableName: "maxNumber", }, - booleanListChildren: { + booleanChildren: { dependencyType: "child", - childGroups: ["booleanLists"], - variableNames: ["numComponents"], + childGroups: ["booleans"], }, - booleanAndBooleanListChildren: { - dependencyType: "child", - childGroups: ["booleans", "booleanLists"], - skipComponentNames: true, + booleansShadow: { + dependencyType: "stateVariable", + variableName: "booleansShadow", }, }; }, - definition: function ({ dependencyValues, componentInfoObjects }) { + definition: function ({ dependencyValues }) { let numComponents = 0; - let childIndexByArrayKey = []; - - let nBooleanLists = 0; - for (let [ - childInd, - child, - ] of dependencyValues.booleanAndBooleanListChildren.entries()) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "booleanList", - }) - ) { - let booleanListChild = - dependencyValues.booleanListChildren[nBooleanLists]; - nBooleanLists++; - for ( - let i = 0; - i < booleanListChild.stateValues.numComponents; - i++ - ) { - childIndexByArrayKey[numComponents + i] = [ - childInd, - i, - ]; - } - numComponents += - booleanListChild.stateValues.numComponents; - } else { - childIndexByArrayKey[numComponents] = [childInd, 0]; - numComponents += 1; - } + let childNameByComponent = []; + + if (dependencyValues.booleanChildren.length > 0) { + childNameByComponent = dependencyValues.booleanChildren.map( + (x) => x.componentName, + ); + numComponents = dependencyValues.booleanChildren.length; + } else if (dependencyValues.booleansShadow !== null) { + numComponents = dependencyValues.booleansShadow.length; } let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && numComponents > maxNum) { + if (numComponents > maxNum) { numComponents = maxNum; - childIndexByArrayKey = childIndexByArrayKey.slice( + childNameByComponent = childNameByComponent.slice( 0, maxNum, ); } return { - setValue: { numComponents, childIndexByArrayKey }, + setValue: { numComponents, childNameByComponent }, checkForActualChange: { numComponents: true }, }; }, }; stateVariableDefinitions.booleans = { - public: true, shadowingInstructions: { createComponentOfType: "boolean", }, isArray: true, entryPrefixes: ["boolean"], - stateVariablesDeterminingDependencies: ["childIndexByArrayKey"], + stateVariablesDeterminingDependencies: ["childNameByComponent"], returnArraySizeDependencies: () => ({ numComponents: { dependencyType: "stateVariable", @@ -174,28 +170,26 @@ export default class BooleanList extends InlineComponent { returnArrayDependenciesByKey({ arrayKeys, stateValues }) { let dependenciesByKey = {}; let globalDependencies = { - childIndexByArrayKey: { + childNameByComponent: { + dependencyType: "stateVariable", + variableName: "childNameByComponent", + }, + booleansShadow: { dependencyType: "stateVariable", - variableName: "childIndexByArrayKey", + variableName: "booleansShadow", }, }; for (let arrayKey of arrayKeys) { let childIndices = []; - let booleanIndex = "1"; - if (stateValues.childIndexByArrayKey[arrayKey]) { - childIndices = [ - stateValues.childIndexByArrayKey[arrayKey][0], - ]; - booleanIndex = - stateValues.childIndexByArrayKey[arrayKey][1] + 1; + if (stateValues.childNameByComponent[arrayKey]) { + childIndices = [arrayKey]; } dependenciesByKey[arrayKey] = { - booleanAndBooleanListChildren: { + booleanChildren: { dependencyType: "child", - childGroups: ["booleans", "booleanLists"], - variableNames: ["value", "boolean" + booleanIndex], - variablesOptional: true, + childGroups: ["booleans"], + variableNames: ["value"], childIndices, }, }; @@ -212,20 +206,13 @@ export default class BooleanList extends InlineComponent { for (let arrayKey of arrayKeys) { let child = - dependencyValuesByKey[arrayKey] - .booleanAndBooleanListChildren[0]; + dependencyValuesByKey[arrayKey].booleanChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - booleans[arrayKey] = child.stateValues.value; - } else { - let booleanIndex = - globalDependencyValues.childIndexByArrayKey[ - arrayKey - ][1] + 1; - booleans[arrayKey] = - child.stateValues["boolean" + booleanIndex]; - } + booleans[arrayKey] = child.stateValues.value; + } else if (globalDependencyValues.booleansShadow !== null) { + booleans[arrayKey] = + globalDependencyValues.booleansShadow[arrayKey]; } } @@ -236,7 +223,7 @@ export default class BooleanList extends InlineComponent { globalDependencyValues, dependencyValuesByKey, dependencyNamesByKey, - arraySize, + workspace, }) { let instructions = []; @@ -246,35 +233,29 @@ export default class BooleanList extends InlineComponent { } let child = - dependencyValuesByKey[arrayKey] - .booleanAndBooleanListChildren[0]; + dependencyValuesByKey[arrayKey].booleanChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .booleanAndBooleanListChildren, - desiredValue: - desiredStateVariableValues.booleans[ - arrayKey - ], - childIndex: 0, - variableIndex: 0, - }); - } else { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .booleanAndBooleanListChildren, - desiredValue: - desiredStateVariableValues.booleans[ - arrayKey - ], - childIndex: 0, - variableIndex: 1, - }); + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey].booleanChildren, + desiredValue: + desiredStateVariableValues.booleans[arrayKey], + childIndex: 0, + variableIndex: 0, + }); + } else if (globalDependencyValues.booleansShadow !== null) { + if (!workspace.desiredBooleanShadow) { + workspace.desiredBooleanShadow = [ + ...globalDependencyValues.booleansShadow, + ]; } + workspace.desiredBooleanShadow[arrayKey] = + desiredStateVariableValues.booleans[arrayKey]; + instructions.push({ + setDependency: "booleansShadow", + desiredValue: workspace.desiredBooleanShadow, + }); } } @@ -295,139 +276,188 @@ export default class BooleanList extends InlineComponent { targetVariableName: "booleans", }; - stateVariableDefinitions.componentNamesInList = { + stateVariableDefinitions.readyToExpandWhenResolved = { returnDependencies: () => ({ - booleanAndBooleanListChildren: { - dependencyType: "child", - childGroups: ["booleans", "booleanLists"], - variableNames: ["componentNamesInList"], - variablesOptional: true, - }, - maxNumber: { + childNameByComponent: { dependencyType: "stateVariable", - variableName: "maxNumber", + variableName: "childNameByComponent", }, }), - definition: function ({ dependencyValues, componentInfoObjects }) { - let componentNamesInList = []; - - for (let child of dependencyValues.booleanAndBooleanListChildren) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "booleanList", - }) - ) { - componentNamesInList.push( - ...child.stateValues.componentNamesInList, - ); - } else { - componentNamesInList.push(child.componentName); - } - } - - let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && componentNamesInList.length > maxNum) { - maxNum = Math.max(0, Math.floor(maxNum)); - componentNamesInList = componentNamesInList.slice( - 0, - maxNum, - ); - } - - return { setValue: { componentNamesInList } }; + // When this state variable is marked stale + // it indicates we should update replacements. + // For this to work, must set + // stateVariableToEvaluateAfterReplacements + // to this variable so that it is marked fresh + markStale: () => ({ updateReplacements: true }), + definition: function () { + return { setValue: { readyToExpandWhenResolved: true } }; }, }; - stateVariableDefinitions.numComponentsToDisplayByChild = { - additionalStateVariablesDefined: ["numChildrenToRender"], - returnDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - booleanListChildren: { - dependencyType: "child", - childGroups: ["booleanLists"], - variableNames: ["numComponents"], - }, - booleanAndBooleanListChildren: { - dependencyType: "child", - childGroups: ["booleans", "booleanLists"], - skipComponentNames: true, - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "booleanList", - variableName: "numComponentsToDisplayByChild", - }, - }), - definition: function ({ - dependencyValues, + return stateVariableDefinitions; + } + + static async createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }) { + let errors = []; + let warnings = []; + + let replacements = []; + let componentsCopied = []; + + let attributesToConvert = {}; + for (let attr of ["fixed", "isResponse"]) { + if (attr in component.attributes) { + attributesToConvert[attr] = component.attributes[attr]; + } + } + + let newNamespace = component.attributes.newNamespace?.primitive; + + // allow one to override the fixed and isResponse attributes + // as well as rounding settings + // by specifying it on the sequence + let attributesFromComposite = {}; + + if (Object.keys(attributesToConvert).length > 0) { + attributesFromComposite = convertAttributesForComponentType({ + attributes: attributesToConvert, + componentType: "boolean", componentInfoObjects, - componentName, - }) { - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent booleanList, which could have limited - // boolean of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } + compositeCreatesNewNamespace: newNamespace, + }); + } - let numComponentsToDisplayByChild = {}; + let childNameByComponent = + await component.stateValues.childNameByComponent; - let numComponentsSoFar = 0; - let numChildrenToRender = 0; + if (childNameByComponent.length > 0) { + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; - let nBooleanLists = 0; - for (let child of dependencyValues.booleanAndBooleanListChildren) { - let numComponentsLeft = Math.max( - 0, - numComponentsToDisplay - numComponentsSoFar, - ); - if (numComponentsLeft > 0) { - numChildrenToRender++; - } - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "booleanList", - }) - ) { - let booleanListChild = - dependencyValues.booleanListChildren[nBooleanLists]; - nBooleanLists++; - - let numComponentsForBooleanListChild = Math.min( - numComponentsLeft, - booleanListChild.stateValues.numComponents, - ); - - numComponentsToDisplayByChild[ - booleanListChild.componentName - ] = numComponentsForBooleanListChild; - numComponentsSoFar += numComponentsForBooleanListChild; - } else { - numComponentsSoFar += 1; + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); + + let repl = await replacementSource.serialize({ + primitiveSourceAttributesToIgnore: ["isResponse"], + }); + if (!repl.attributes) { + repl.attributes = {}; } + Object.assign( + repl.attributes, + JSON.parse(JSON.stringify(attributesFromComposite)), + ); + replacements.push(repl); } - - return { - setValue: { - numComponentsToDisplayByChild, - numChildrenToRender, + } + } else { + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + replacements.push({ + componentType: "boolean", + attributes: JSON.parse( + JSON.stringify(attributesFromComposite), + ), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `boolean${i + 1}`, + }, + ], }, - }; - }, - markStale: () => ({ updateRenderedChildren: true }), + }); + } + } + + workspace.uniqueIdentifiersUsed = []; + replacements = postProcessCopy({ + serializedComponents: replacements, + componentName: component.componentName, + uniqueIdentifiersUsed: workspace.uniqueIdentifiersUsed, + addShadowDependencies: true, + markAsPrimaryShadow: true, + }); + + let processResult = processAssignNames({ + assignNames: component.doenetAttributes.assignNames, + serializedComponents: replacements, + parentName: component.componentName, + parentCreatesNewNamespace: newNamespace, + componentInfoObjects, + }); + errors.push(...processResult.errors); + warnings.push(...processResult.warnings); + + workspace.componentsCopied = componentsCopied; + + return { + replacements: processResult.serializedComponents, + errors, + warnings, }; + } - return stateVariableDefinitions; + static async calculateReplacementChanges({ + component, + components, + componentInfoObjects, + workspace, + }) { + // TODO: don't yet have a way to return errors and warnings! + let errors = []; + let warnings = []; + + let componentsToCopy = []; + + let childNameByComponent = + await component.stateValues.childNameByComponent; + + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; + + if (replacementSource) { + componentsToCopy.push(replacementSource.componentName); + } + } + + if ( + componentsToCopy.length == workspace.componentsCopied.length && + workspace.componentsCopied.every( + (x, i) => x === componentsToCopy[i], + ) + ) { + return []; + } + + // for now, just recreate + let replacementResults = await this.createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }); + + let replacements = replacementResults.replacements; + errors.push(...replacementResults.errors); + warnings.push(...replacementResults.warnings); + + let replacementChanges = [ + { + changeType: "add", + changeTopLevelReplacements: true, + firstReplacementInd: 0, + numberReplacementsToReplace: component.replacements.length, + serializedReplacements: replacements, + }, + ]; + + return replacementChanges; } } diff --git a/packages/doenetml-worker/src/components/Math.js b/packages/doenetml-worker/src/components/Math.js index 2ce8a8a91..cc9c25924 100644 --- a/packages/doenetml-worker/src/components/Math.js +++ b/packages/doenetml-worker/src/components/Math.js @@ -27,6 +27,8 @@ import { unicodeToSuperSubscripts, preprocessMathInverseDefinition, } from "../utils/math"; +import { createInputStringFromChildren } from "../utils/parseMath"; +import { returnMathVectorMatrixStateVariableDefinitions } from "../utils/mathVectorMatrixStateVariables"; const vectorAndListOperators = ["list", ...vectorOperators]; @@ -246,10 +248,25 @@ export default class MathComponent extends InlineComponent { childGroups: ["maths"], variableNames: ["unordered"], }, + sourceCompositeUnordered: { + dependencyType: "sourceCompositeStateVariable", + variableName: "unordered", + }, }), - definition({ dependencyValues }) { + definition({ dependencyValues, usedDefault }) { if (dependencyValues.unorderedAttr === null) { - if (dependencyValues.mathChildren.length > 0) { + if ( + dependencyValues.sourceCompositeUnordered !== null && + !usedDefault.sourceCompositeUnordered + ) { + return { + setValue: { + unordered: Boolean( + dependencyValues.sourceCompositeUnordered, + ), + }, + }; + } else if (dependencyValues.mathChildren.length > 0) { let unordered = dependencyValues.mathChildren.every( (x) => x.stateValues.unordered, ); @@ -1056,630 +1073,10 @@ export default class MathComponent extends InlineComponent { }, }; - stateVariableDefinitions.numDimensions = { - public: true, - shadowingInstructions: { - createComponentOfType: "integer", - }, - returnDependencies: () => ({ - value: { - dependencyType: "stateVariable", - variableName: "value", - }, - }), - definition({ dependencyValues }) { - let numDimensions = 1; - - let tree = dependencyValues.value.tree; - - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - numDimensions = tree.length - 1; - } else if (tree[0] === "matrix") { - let size = tree[1].slice(1); - - if (size[0] === 1) { - numDimensions = size[1]; - } else if (size[1] === 1) { - numDimensions = size[0]; - } - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - numDimensions = tree[1].length - 1; - } - } - - return { setValue: { numDimensions } }; - }, - }; - - stateVariableDefinitions.vector = { - public: true, - shadowingInstructions: { - createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - returnWrappingComponents(prefix) { - if (prefix === "x") { - return []; - } else { - // entire array - // wrap by both and - return [ - [ - "vector", - { - componentType: "mathList", - isAttributeNamed: "xs", - }, - ], - ]; - } - }, - }, - isArray: true, - entryPrefixes: ["x"], - returnArraySizeDependencies: () => ({ - numDimensions: { - dependencyType: "stateVariable", - variableName: "numDimensions", - }, - }), - returnArraySize({ dependencyValues }) { - return [dependencyValues.numDimensions]; - }, - returnArrayDependenciesByKey() { - let globalDependencies = { - value: { - dependencyType: "stateVariable", - variableName: "value", - }, - }; - return { globalDependencies }; - }, - arrayDefinitionByKey({ globalDependencyValues, arraySize }) { - let tree = globalDependencyValues.value.tree; - - let createdVector = false; - - let vector = {}; - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - for (let ind = 0; ind < arraySize[0]; ind++) { - vector[ind] = me.fromAst(tree[ind + 1]); - } - createdVector = true; - } else if (tree[0] === "matrix") { - let size = tree[1].slice(1); - if (size[0] === 1) { - for (let ind = 0; ind < arraySize[0]; ind++) { - vector[ind] = me.fromAst(tree[2][1][ind + 1]); - } - createdVector = true; - } else if (size[1] === 1) { - for (let ind = 0; ind < arraySize[0]; ind++) { - vector[ind] = me.fromAst(tree[2][ind + 1][1]); - } - createdVector = true; - } - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - for (let ind = 0; ind < arraySize[0]; ind++) { - vector[ind] = me.fromAst(tree[1][ind + 1]); - } - createdVector = true; - } - } - if (!createdVector) { - vector[0] = globalDependencyValues.value; - } - - return { setValue: { vector } }; - }, - async inverseArrayDefinitionByKey({ - desiredStateVariableValues, - globalDependencyValues, - stateValues, - workspace, - arraySize, - }) { - // in case just one ind specified, merge with previous values - if (!workspace.desiredVector) { - workspace.desiredVector = []; - } - for (let ind = 0; ind < arraySize[0]; ind++) { - if (desiredStateVariableValues.vector[ind] !== undefined) { - workspace.desiredVector[ind] = - convertValueToMathExpression( - desiredStateVariableValues.vector[ind], - ); - } else if (workspace.desiredVector[ind] === undefined) { - workspace.desiredVector[ind] = ( - await stateValues.vector - )[ind]; - } - } - - let desiredValue; - let tree = globalDependencyValues.value.tree; - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - desiredValue = me.fromAst([ - tree[0], - ...workspace.desiredVector.map((x) => x.tree), - ]); - } else if (tree[0] === "matrix") { - let size = tree[1].slice(1); - if (size[0] === 1) { - let desiredMatrixVals = ["tuple"]; - for (let ind = 0; ind < arraySize[0]; ind++) { - desiredMatrixVals.push( - workspace.desiredVector[ind], - ); - } - desiredMatrixVals = ["tuple", desiredMatrixVals]; - desiredValue = me.fromAst([ - "matrix", - tree[1], - desiredMatrixVals, - ]); - } else if (size[1] === 1) { - let desiredMatrixVals = ["tuple"]; - for (let ind = 0; ind < arraySize[0]; ind++) { - desiredMatrixVals.push([ - "tuple", - workspace.desiredVector[ind], - ]); - } - desiredValue = me.fromAst([ - "matrix", - tree[1], - desiredMatrixVals, - ]); - } - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - desiredValue = [ - tree[0], - [ - tree[1][0], - ...workspace.desiredVector.map((x) => x.tree), - ], - ]; - if (tree[2]) { - desiredValue.push(tree[2]); - } - desiredValue = me.fromAst(desiredValue); - } - } - - if (!desiredValue) { - desiredValue = workspace.desiredVector[0]; - } - - let instructions = [ - { - setDependency: "value", - desiredValue, - }, - ]; - - return { - success: true, - instructions, - }; - }, - }; - - stateVariableDefinitions.x = { - isAlias: true, - targetVariableName: "x1", - }; - - stateVariableDefinitions.y = { - isAlias: true, - targetVariableName: "x2", - }; - - stateVariableDefinitions.z = { - isAlias: true, - targetVariableName: "x3", - }; - - stateVariableDefinitions.matrixSize = { - public: true, - shadowingInstructions: { - createComponentOfType: "numberList", - }, - returnDependencies: () => ({ - value: { - dependencyType: "stateVariable", - variableName: "value", - }, - }), - definition({ dependencyValues }) { - let matrixSize = [1, 1]; - - let tree = dependencyValues.value.tree; - - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - matrixSize = [tree.length - 1, 1]; - } else if (tree[0] === "matrix") { - matrixSize = tree[1].slice(1); - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - matrixSize = [1, tree[1].length - 1]; - } - } - - return { setValue: { matrixSize } }; - }, - }; - - stateVariableDefinitions.numRows = { - public: true, - shadowingInstructions: { - createComponentOfType: "integer", - }, - returnDependencies: () => ({ - matrixSize: { - dependencyType: "stateVariable", - variableName: "matrixSize", - }, - }), - definition({ dependencyValues }) { - return { - setValue: { numRows: dependencyValues.matrixSize[0] }, - }; - }, - }; - - stateVariableDefinitions.numColumns = { - public: true, - shadowingInstructions: { - createComponentOfType: "integer", - }, - returnDependencies: () => ({ - matrixSize: { - dependencyType: "stateVariable", - variableName: "matrixSize", - }, - }), - definition({ dependencyValues }) { - return { - setValue: { numColumns: dependencyValues.matrixSize[1] }, - }; - }, - }; - - stateVariableDefinitions.matrix = { - public: true, - shadowingInstructions: { - createComponentOfType: "math", - addAttributeComponentsShadowingStateVariables: - returnRoundingAttributeComponentShadowing(), - returnWrappingComponents(prefix) { - if (prefix === "matrixEntry") { - return []; - } else if (prefix === "row") { - return [["matrix", "matrixRow"]]; - } else if (prefix === "column") { - return [["matrix", "matrixColumn"]]; - } else { - // entire matrix - // wrap inner dimension by matrixRow and outer dimension by matrix - return [["matrixRow"], ["matrix"]]; - } - }, - }, - isArray: true, - numDimensions: 2, - entryPrefixes: ["matrixEntry", "row", "column", "rows", "columns"], - returnEntryDimensions: (prefix) => { - if (prefix === "matrixEntry") { - return 0; - } else if (prefix === "rows" || prefix === "columns") { - return 2; - } else { - return 1; - } - }, - getArrayKeysFromVarName({ - arrayEntryPrefix, - varEnding, - arraySize, - }) { - if (arrayEntryPrefix === "matrixEntry") { - // matrixEntry1_2 is the 2nd entry from the first row - let indices = varEnding - .split("_") - .map((x) => Number(x) - 1); - if ( - indices.length === 2 && - indices.every((x) => Number.isInteger(x) && x >= 0) - ) { - if (arraySize) { - if (indices.every((x, i) => x < arraySize[i])) { - return [String(indices)]; - } else { - return []; - } - } else { - // If not given the array size, - // then return the array keys assuming the array is large enough. - // Must do this as it is used to determine potential array entries. - return [String(indices)]; - } - } else { - return []; - } - } else if (arrayEntryPrefix === "row") { - // row3 is all components of the third row - - let rowInd = Number(varEnding) - 1; - if (!(Number.isInteger(rowInd) && rowInd >= 0)) { - return []; - } - - if (!arraySize) { - // If don't have array size, we just need to determine if it is a potential entry. - // Return the first entry assuming array is large enough - return [rowInd + ",0"]; - } - if (rowInd < arraySize[0]) { - // array of "rowInd,i", where i=0, ..., arraySize[1]-1 - return Array.from( - Array(arraySize[1]), - (_, i) => rowInd + "," + i, - ); - } else { - return []; - } - } else if (arrayEntryPrefix === "column") { - // column3 is all components of the third column - - let colInd = Number(varEnding) - 1; - if (!(Number.isInteger(colInd) && colInd >= 0)) { - return []; - } - - if (!arraySize) { - // If don't have array size, we just need to determine if it is a potential entry. - // Return the first entry assuming array is large enough - return ["0," + colInd]; - } - if (colInd < arraySize[1]) { - // array of "i,colInd", where i=0, ..., arraySize[1]-1 - return Array.from( - Array(arraySize[0]), - (_, i) => i + "," + colInd, - ); - } else { - return []; - } - } else if ( - arrayEntryPrefix === "rows" || - arrayEntryPrefix === "columns" - ) { - // rows or columns is the whole matrix - // (this are designed for getting rows and columns using propIndex) - // (rows and matrix are the same, but rows is added to be parallel to columns) - - if (!arraySize) { - // If don't have array size, we justr eturn the first entry - return ["0,0"]; - } - let keys = []; - for (let rowInd = 0; rowInd < arraySize[0]; rowInd++) { - keys.push( - ...Array.from( - Array(arraySize[1]), - (_, i) => rowInd + "," + i, - ), - ); - } - return keys; - } - }, - arrayVarNameFromPropIndex(propIndex, varName) { - if (varName === "matrix" || varName === "rows") { - if (propIndex.length === 1) { - return "row" + propIndex[0]; - } else { - // if propIndex has additional entries, ignore them - return `matrixEntry${propIndex[0]}_${propIndex[1]}`; - } - } - if (varName === "columns") { - if (propIndex.length === 1) { - return "column" + propIndex[0]; - } else { - // if propIndex has additional entries, ignore them - return `matrixEntry${propIndex[1]}_${propIndex[0]}`; - } - } - if (varName.slice(0, 3) === "row") { - let rowNum = Number(varName.slice(3)); - if (Number.isInteger(rowNum) && rowNum > 0) { - // if propIndex has additional entries, ignore them - return `matrixEntry${rowNum}_${propIndex[0]}`; - } - } - if (varName.slice(0, 6) === "column") { - let colNum = Number(varName.slice(6)); - if (Number.isInteger(colNum) && colNum > 0) { - // if propIndex has additional entries, ignore them - return `matrixEntry${propIndex[0]}_${colNum}`; - } - } - return null; - }, - returnArraySizeDependencies: () => ({ - matrixSize: { - dependencyType: "stateVariable", - variableName: "matrixSize", - }, - }), - returnArraySize({ dependencyValues }) { - return dependencyValues.matrixSize; - }, - returnArrayDependenciesByKey() { - let globalDependencies = { - value: { - dependencyType: "stateVariable", - variableName: "value", - }, - }; - return { globalDependencies }; - }, - arrayDefinitionByKey({ globalDependencyValues, arraySize }) { - let tree = globalDependencyValues.value.tree; - - let createdMatrix = false; - - let matrix = {}; - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - for (let ind = 0; ind < arraySize[0]; ind++) { - matrix[ind + ",0"] = me.fromAst(tree[ind + 1]); - } - createdMatrix = true; - } else if (tree[0] === "matrix") { - let matVals = tree[2]; - for (let i = 0; i < arraySize[0]; i++) { - for (let j = 0; j < arraySize[1]; j++) { - matrix[`${i},${j}`] = me.fromAst( - matVals[i + 1][j + 1], - ); - } - } - createdMatrix = true; - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - for (let ind = 0; ind < arraySize[1]; ind++) { - matrix["0," + ind] = me.fromAst(tree[1][ind + 1]); - } - createdMatrix = true; - } - } - if (!createdMatrix) { - matrix["0,0"] = globalDependencyValues.value; - } - - return { setValue: { matrix } }; - }, - async inverseArrayDefinitionByKey({ - desiredStateVariableValues, - globalDependencyValues, - stateValues, - workspace, - arraySize, - }) { - // in case just one ind specified, merge with previous values - if (!workspace.desiredMatrix) { - workspace.desiredMatrix = []; - } - for (let i = 0; i < arraySize[0]; i++) { - for (let j = 0; j < arraySize[1]; j++) { - let arrayKey = i + "," + j; - if ( - desiredStateVariableValues.matrix[arrayKey] !== - undefined - ) { - workspace.desiredMatrix[arrayKey] = - convertValueToMathExpression( - desiredStateVariableValues.matrix[arrayKey], - ); - } else if ( - workspace.desiredMatrix[arrayKey] === undefined - ) { - workspace.desiredMatrix[arrayKey] = ( - await stateValues.matrix - )[i][j]; - } - } - } - - let desiredValue; - let tree = globalDependencyValues.value.tree; - if (Array.isArray(tree)) { - if (vectorAndListOperators.includes(tree[0])) { - desiredValue = [tree[0]]; - for (let ind = 0; ind < arraySize[0]; ind++) { - desiredValue.push( - workspace.desiredMatrix[ind + ",0"].tree, - ); - } - } else if (tree[0] === "matrix") { - let desiredMatrixVals = ["tuple"]; - - for (let i = 0; i < arraySize[0]; i++) { - let row = ["tuple"]; - for (let j = 0; j < arraySize[1]; j++) { - row.push( - workspace.desiredMatrix[`${i},${j}`].tree, - ); - } - desiredMatrixVals.push(row); - } - desiredValue = me.fromAst([ - "matrix", - tree[1], - desiredMatrixVals, - ]); - } else if ( - vectorOperators.includes(tree[1][0]) && - ((tree[0] === "^" && tree[2] === "T") || - tree[0] === "prime") - ) { - desiredValue = [tree[0]]; - let desiredVector = [tree[1][0]]; - for (let ind = 0; ind < arraySize[1]; ind++) { - desiredVector.push( - workspace.desiredMatrix["0," + ind].tree, - ); - } - desiredValue = [tree[0], desiredVector]; - if (tree[2]) { - desiredValue.push(tree[2]); - } - desiredValue = me.fromAst(desiredValue); - } - } - - if (!desiredValue) { - desiredValue = workspace.desiredMatrix["0,0"]; - } - - let instructions = [ - { - setDependency: "value", - desiredValue, - }, - ]; - - return { - success: true, - instructions, - }; - }, - }; + Object.assign( + stateVariableDefinitions, + returnMathVectorMatrixStateVariableDefinitions(), + ); return stateVariableDefinitions; } @@ -1812,14 +1209,6 @@ function calculateExpressionWithCodes({ dependencyValues, changes }) { } } - let inputString = createInputStringFromChildren({ - stringMathChildren: dependencyValues.stringMathChildren, - codePre: dependencyValues.codePre, - format: dependencyValues.format, - }); - - let expressionWithCodes = null; - let functionSymbols = [...dependencyValues.functionSymbols]; functionSymbols.push( ...dependencyValues.mathChildrenFunctionSymbols.map( @@ -1827,340 +1216,51 @@ function calculateExpressionWithCodes({ dependencyValues, changes }) { ), ); - if (inputString === "") { - expressionWithCodes = me.fromAst("\uFF3F"); // long underscore - } else { - if (dependencyValues.format === "text") { - let fromText = getTextToMathConverter({ - functionSymbols, - splitSymbols: dependencyValues.splitSymbols, - parseScientificNotation: - dependencyValues.parseScientificNotation, - }); - try { - expressionWithCodes = fromText(inputString); - } catch (e) { - expressionWithCodes = me.fromAst("\uFF3F"); // long underscore - console.log( - "Invalid value for a math of text format: " + inputString, - ); - } - } else if (dependencyValues.format === "latex") { - let fromLatex = getLatexToMathConverter({ - functionSymbols, - splitSymbols: dependencyValues.splitSymbols, - parseScientificNotation: - dependencyValues.parseScientificNotation, - }); - try { - expressionWithCodes = fromLatex(inputString); - } catch (e) { - expressionWithCodes = me.fromAst("\uFF3F"); // long underscore - console.log( - "Invalid value for a math of latex format: " + inputString, - ); - } - } - } + let parser; - return { - setValue: { expressionWithCodes }, - setEssentialValue: { expressionWithCodes }, - }; -} - -// concatenate strings and with a numbered code for each math child -// (that will be parsed to form expression with codes) -// If compositeReplacementAsList is true, -// then add commas between the components that are all from one composite, -// if that composite has asList set to true. -// Put parens around that list in some cases, as described below. -function createInputStringFromChildren({ - stringMathChildren, - codePre, - format, -}) { - let mathInd = 0; - let mathIndByChild = []; - for (let child of stringMathChildren) { - if (typeof child === "string") { - mathIndByChild.push(null); - } else { - mathIndByChild.push(mathInd); - mathInd++; - } + if (dependencyValues.format === "text") { + parser = getTextToMathConverter({ + functionSymbols, + splitSymbols: dependencyValues.splitSymbols, + parseScientificNotation: dependencyValues.parseScientificNotation, + }); + } else if (dependencyValues.format === "latex") { + parser = getLatexToMathConverter({ + functionSymbols, + splitSymbols: dependencyValues.splitSymbols, + parseScientificNotation: dependencyValues.parseScientificNotation, + }); } - let result = createInputStringFromChildrenSub({ - compositeReplacementRange: stringMathChildren.compositeReplacementRange, - stringMathChildren, - startInd: 0, - endInd: stringMathChildren.length - 1, - mathIndByChild, - format, - codePre, + let stringResults = createInputStringFromChildren({ + children: dependencyValues.stringMathChildren, + codePre: dependencyValues.codePre, + format: dependencyValues.format, + parser, }); - return result.newChildren.join(""); -} + let inputString = stringResults.string; -function createInputStringFromChildrenSub({ - compositeReplacementRange, - stringMathChildren, - startInd, - endInd, - mathIndByChild, - format, - codePre, - potentialListComponents = null, -}) { - let newChildren = []; - let newPotentialListComponents = []; - let lastChildInd = startInd - 1; - - for ( - let rangeInd = 0; - rangeInd < compositeReplacementRange.length; - rangeInd++ - ) { - let range = compositeReplacementRange[rangeInd]; - - let rangeFirstInd = range.firstInd; - let rangeLastInd = range.lastInd; - - if (rangeFirstInd > lastChildInd && rangeLastInd <= endInd) { - if (lastChildInd + 1 < rangeFirstInd) { - for (let ind = lastChildInd + 1; ind < rangeFirstInd; ind++) { - // we are not grouping these children - // but we are separately turning each one into a string - // (turning the math children into a code based on codePre and its mathInd) - newChildren.push( - baseStringFromChildren({ - stringMathChildren, - startInd: ind, - endInd: ind, - mathIndByChild, - format, - codePre, - }), - ); - } - if (potentialListComponents) { - // Since we didn't change the components, - // their status of being a potential list component is not changed - newPotentialListComponents.push( - ...potentialListComponents.slice( - lastChildInd - startInd + 1, - rangeFirstInd - startInd, - ), - ); - } - } - - // If a composite produced composites that produced children, - // then this outer composite is first in the array of replacement ranges. - // We first process the children corresponding to any of these replacement composites, - // which will concatenate the replacements of each composite into a single text, - // which may be be turned into a list according to the settings of that composite. - - // We remove the replacement range of the current composite (any all earlier ones) - let subReplacementRange = compositeReplacementRange.slice( - rangeInd + 1, - ); - - let { - newChildren: childrenInRange, - newPotentialListComponents: potentialListComponentsInRange, - } = createInputStringFromChildrenSub({ - compositeReplacementRange: subReplacementRange, - stringMathChildren, - startInd: rangeFirstInd, - endInd: rangeLastInd, - mathIndByChild, - format, - codePre, - potentialListComponents: range.potentialListComponents, - }); - - let allAreListComponents = potentialListComponentsInRange.every( - (x) => x, - ); - - if ( - range.asList && - allAreListComponents && - childrenInRange.length > 1 - ) { - // add commas between all children from a single composite - let listString = childrenInRange - .filter((v) => v.trim() !== "") - .map((v, i, a) => (i === a.length - 1 ? v : v.trimEnd())) - .join(", "); - - // The following implements the logic to determine if this comma-separated list - // should be wrapped by parens. - // Wrap with parens if the lists is surrounded by a non-delimiter on either side. - // The parens will generally turn the list into a tuple (or to arguments of a function) - // when it is parsed into a math-expression. - - let wrap = false; - - // First check if there is a non-delimiter to the left - if (rangeFirstInd > 0) { - let prevInd = rangeFirstInd - 1; - while (prevInd >= 0) { - let prevChild = stringMathChildren[prevInd]; - if (typeof prevChild === "string") { - prevChild = prevChild.trim(); - if (prevChild.length > 0) { - if ( - !["{", "[", "(", "|", ","].includes( - prevChild[prevChild.length - 1], - ) - ) { - // The string to the left did not contain one of the delimiters, - // so we must wrap the list. - wrap = true; - } - break; - } - } else { - // There is a math child to the left, - // so we must wrap the list - wrap = true; - } - } - } - - if (!wrap) { - // Since we didn't have a non-delimiter to the left, - // check if there is a non-delimiter to the right. - if (rangeLastInd < stringMathChildren.length - 1) { - let nextInd = rangeLastInd + 1; - while (nextInd <= stringMathChildren.length - 1) { - let nextChild = stringMathChildren[nextInd]; - if (typeof nextChild === "string") { - nextChild = nextChild.trim(); - if (nextChild.length > 0) { - let nextChar = nextChild[0]; - // If the format is latex, - // the delimiter could be escaped by a \ - if ( - format === "latex" && - nextChar === "\\" && - nextChild.length > 1 - ) { - nextChar = nextChild[1]; - } - if ( - !["}", "]", ")", "|", ","].includes( - nextChar, - ) - ) { - // The string to the right did not contain one of the delimiters, - // so we must wrap the list. - wrap = true; - } - break; - } - } else { - // There is a math child to the right, - // so we must wrap the list - wrap = true; - } - } - } - } - - if (wrap) { - listString = "(" + listString + ")"; - } - - newChildren.push(listString); - } else { - // We are not turning the children in a list, - // so just concatenate the strings - newChildren.push(childrenInRange.join("")); - } - - if (potentialListComponents) { - // record whether the result from the composite (a single string now) - // should be considered a list component for any outer composite - newPotentialListComponents.push(allAreListComponents); - } - lastChildInd = rangeLastInd; - } - } - - if (lastChildInd < endInd) { - for (let ind = lastChildInd + 1; ind <= endInd; ind++) { - // we are not grouping these children - // but we are separately turning each one into a string - // (turning the math children into a code based on codePre and its mathInd) - newChildren.push( - baseStringFromChildren({ - stringMathChildren, - startInd: ind, - endInd: ind, - mathIndByChild, - format, - codePre, - }), - ); - } + let expressionWithCodes = null; - if (potentialListComponents) { - // Since we didn't change the components, - // their status of being a potential list component is not changed - newPotentialListComponents.push( - ...potentialListComponents.slice( - lastChildInd - startInd + 1, - endInd - startInd + 1, - ), + if (inputString === "") { + expressionWithCodes = me.fromAst("\uFF3F"); // long underscore + } else { + try { + expressionWithCodes = parser(inputString); + } catch (e) { + expressionWithCodes = me.fromAst("\uFF3F"); // long underscore + console.log( + `Invalid value for a math of ${dependencyValues.format} format: ` + + inputString, ); } } - return { newChildren, newPotentialListComponents }; -} - -// concatenate string children and codes from math-children -// into a single string to be parsed into a math component -function baseStringFromChildren({ - stringMathChildren, - startInd, - endInd, - mathIndByChild, - format, - codePre, -}) { - let str = ""; - - for (let ind = startInd; ind <= endInd; ind++) { - let child = stringMathChildren[ind]; - if (typeof child === "string") { - str += " " + child + " "; - } else { - // a math - let code = codePre + mathIndByChild[ind]; - - let nextString; - if (format === "latex") { - // for latex, must explicitly denote that code - // is a multicharacter variable - nextString = "\\operatorname{" + code + "}"; - } else { - // for text, just make sure code is surrounded by spaces - // (the presence of numbers inside code will ensure that - // it is parsed as a multicharcter variable) - nextString = " " + code + " "; - } - - str += nextString; - } - } - - return str; + return { + setValue: { expressionWithCodes }, + setEssentialValue: { expressionWithCodes }, + }; } function calculateMathValue({ dependencyValues } = {}) { diff --git a/packages/doenetml-worker/src/components/MathInput.js b/packages/doenetml-worker/src/components/MathInput.js index 3144e485e..a7bec2a08 100644 --- a/packages/doenetml-worker/src/components/MathInput.js +++ b/packages/doenetml-worker/src/components/MathInput.js @@ -16,6 +16,7 @@ import { roundForDisplay, stripLatex, } from "../utils/math"; +import { returnMathVectorMatrixStateVariableDefinitions } from "../utils/mathVectorMatrixStateVariables"; export default class MathInput extends Input { constructor(args) { @@ -795,6 +796,11 @@ export default class MathInput extends Input { definition: () => ({ setValue: { componentType: "math" } }), }; + Object.assign( + stateVariableDefinitions, + returnMathVectorMatrixStateVariableDefinitions(), + ); + return stateVariableDefinitions; } diff --git a/packages/doenetml-worker/src/components/MathList.js b/packages/doenetml-worker/src/components/MathList.js index b4c055abc..a150abf79 100644 --- a/packages/doenetml-worker/src/components/MathList.js +++ b/packages/doenetml-worker/src/components/MathList.js @@ -50,6 +50,10 @@ export default class MathList extends CompositeComponent { leaveRaw: true, }; + attributes.isResponse = { + leaveRaw: true, + }; + for (let attrName in returnRoundingAttributes()) { attributes[attrName] = { leaveRaw: true, @@ -102,26 +106,8 @@ export default class MathList extends CompositeComponent { }); sugarInstructions.push({ - replacementFunction: function ({ - matchedChildren, - componentAttributes, - }) { - let result = groupIntoMathsSeparatedBySpaces({ - matchedChildren, - }); - - // Since an answer ignores composite descendants when calculating responses, - // we need to add isResponse from the mathList to its children. - if (componentAttributes.isResponse) { - for (let child of result.newChildren) { - if (!child.attributes) { - child.attributes = {}; - } - child.attributes.isResponse = { primitive: true }; - } - } - - return result; + replacementFunction: function ({ matchedChildren }) { + return groupIntoMathsSeparatedBySpaces({ matchedChildren }); }, }); @@ -556,6 +542,7 @@ export default class MathList extends CompositeComponent { let attributesToConvert = {}; for (let attr of [ "fixed", + "isResponse", ...Object.keys(returnRoundingAttributes()), ]) { if (attr in component.attributes) { @@ -565,7 +552,7 @@ export default class MathList extends CompositeComponent { let newNamespace = component.attributes.newNamespace?.primitive; - // allow one to override the fixed attributes + // allow one to override the fixed and isResponse attributes // as well as rounding settings // by specifying it on the mathList let attributesFromComposite = {}; diff --git a/packages/doenetml-worker/src/components/Number.js b/packages/doenetml-worker/src/components/Number.js index dab706b48..a8bba8a1f 100644 --- a/packages/doenetml-worker/src/components/Number.js +++ b/packages/doenetml-worker/src/components/Number.js @@ -149,6 +149,36 @@ export default class NumberComponent extends InlineComponent { }); Object.assign(stateVariableDefinitions, roundingDefinitions); + stateVariableDefinitions.inUnorderedList = { + defaultValue: false, + returnDependencies: () => ({ + sourceCompositeUnordered: { + dependencyType: "sourceCompositeStateVariable", + variableName: "unordered", + }, + }), + definition({ dependencyValues, usedDefault }) { + if ( + dependencyValues.sourceCompositeUnordered !== null && + !usedDefault.sourceCompositeUnordered + ) { + return { + setValue: { + inUnorderedList: Boolean( + dependencyValues.sourceCompositeUnordered, + ), + }, + }; + } else { + return { + setValue: { + inUnorderedList: false, + }, + }; + } + }, + }; + stateVariableDefinitions.singleNumberOrStringChild = { additionalStateVariablesDefined: ["singleMathChild"], returnDependencies: () => ({ diff --git a/packages/doenetml-worker/src/components/NumberList.js b/packages/doenetml-worker/src/components/NumberList.js index 059ed090f..421d8289c 100644 --- a/packages/doenetml-worker/src/components/NumberList.js +++ b/packages/doenetml-worker/src/components/NumberList.js @@ -42,6 +42,10 @@ export default class NumberList extends CompositeComponent { leaveRaw: true, }; + attributes.isResponse = { + leaveRaw: true, + }; + for (let attrName in returnRoundingAttributes()) { attributes[attrName] = { leaveRaw: true, @@ -63,26 +67,8 @@ export default class NumberList extends CompositeComponent { }); sugarInstructions.push({ - replacementFunction: function ({ - matchedChildren, - componentAttributes, - }) { - let result = groupIntoNumbersSeparatedBySpaces({ - matchedChildren, - }); - - // Since an answer ignores composite descendants when calculating responses, - // we need to add isResponse from the numberList to its children. - if (componentAttributes.isResponse) { - for (let child of result.newChildren) { - if (!child.attributes) { - child.attributes = {}; - } - child.attributes.isResponse = { primitive: true }; - } - } - - return result; + replacementFunction: function ({ matchedChildren }) { + return groupIntoNumbersSeparatedBySpaces({ matchedChildren }); }, }); @@ -162,10 +148,7 @@ export default class NumberList extends CompositeComponent { } return { - setValue: { - numComponents, - childNameByComponent, - }, + setValue: { numComponents, childNameByComponent }, checkForActualChange: { numComponents: true }, }; }, diff --git a/packages/doenetml-worker/src/components/Text.js b/packages/doenetml-worker/src/components/Text.js index 8940229a2..8ec308677 100644 --- a/packages/doenetml-worker/src/components/Text.js +++ b/packages/doenetml-worker/src/components/Text.js @@ -7,7 +7,10 @@ import { returnSelectedStyleStateVariableDefinition, returnTextStyleDescriptionDefinitions, } from "@doenet/utils"; -import { textFromChildren } from "../utils/text"; +import { + returnTextPieceStateVariableDefinitions, + textFromChildren, +} from "../utils/text"; import { getLatexToMathConverter, getTextToMathConverter } from "../utils/math"; import InlineComponent from "./abstract/InlineComponent"; import me from "math-expressions"; @@ -93,6 +96,36 @@ export default class Text extends InlineComponent { let anchorDefinition = returnAnchorStateVariableDefinition(); Object.assign(stateVariableDefinitions, anchorDefinition); + stateVariableDefinitions.inUnorderedList = { + defaultValue: false, + returnDependencies: () => ({ + sourceCompositeUnordered: { + dependencyType: "sourceCompositeStateVariable", + variableName: "unordered", + }, + }), + definition({ dependencyValues, usedDefault }) { + if ( + dependencyValues.sourceCompositeUnordered !== null && + !usedDefault.sourceCompositeUnordered + ) { + return { + setValue: { + inUnorderedList: Boolean( + dependencyValues.sourceCompositeUnordered, + ), + }, + }; + } else { + return { + setValue: { + inUnorderedList: false, + }, + }; + } + }, + }; + stateVariableDefinitions.value = { public: true, shadowingInstructions: { @@ -168,24 +201,6 @@ export default class Text extends InlineComponent { }, }; - stateVariableDefinitions.numCharacters = { - public: true, - shadowingInstructions: { - createComponentOfType: "integer", - }, - returnDependencies: () => ({ - value: { - dependencyType: "stateVariable", - variableName: "value", - }, - }), - definition({ dependencyValues }) { - return { - setValue: { numCharacters: dependencyValues.value.length }, - }; - }, - }; - stateVariableDefinitions.text = { public: true, shadowingInstructions: { @@ -296,6 +311,9 @@ export default class Text extends InlineComponent { }, }; + let pieceDefs = returnTextPieceStateVariableDefinitions(); + Object.assign(stateVariableDefinitions, pieceDefs); + return stateVariableDefinitions; } diff --git a/packages/doenetml-worker/src/components/TextInput.js b/packages/doenetml-worker/src/components/TextInput.js index ed635f638..bd2c4b624 100644 --- a/packages/doenetml-worker/src/components/TextInput.js +++ b/packages/doenetml-worker/src/components/TextInput.js @@ -7,6 +7,7 @@ import { returnLabelStateVariableDefinitions, returnWrapNonLabelsSugarFunction, } from "../utils/label"; +import { returnTextPieceStateVariableDefinitions } from "../utils/text"; import Input from "./abstract/Input"; export default class Textinput extends Input { @@ -452,6 +453,9 @@ export default class Textinput extends Input { definition: () => ({ setValue: { componentType: "text" } }), }; + let pieceDefs = returnTextPieceStateVariableDefinitions(); + Object.assign(stateVariableDefinitions, pieceDefs); + return stateVariableDefinitions; } diff --git a/packages/doenetml-worker/src/components/TextList.js b/packages/doenetml-worker/src/components/TextList.js index c0d217960..95355573c 100644 --- a/packages/doenetml-worker/src/components/TextList.js +++ b/packages/doenetml-worker/src/components/TextList.js @@ -1,9 +1,16 @@ -import InlineComponent from "./abstract/InlineComponent"; +import CompositeComponent from "./abstract/CompositeComponent"; import { returnGroupIntoComponentTypeSeparatedBySpacesOutsideParens } from "./commonsugar/lists"; +import { + convertAttributesForComponentType, + postProcessCopy, +} from "../utils/copy"; +import { processAssignNames } from "../utils/naming"; -export default class TextList extends InlineComponent { +export default class TextList extends CompositeComponent { static componentType = "textList"; - static renderChildren = true; + + static stateVariableToEvaluateAfterReplacements = + "readyToExpandWhenResolved"; static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -30,10 +37,18 @@ export default class TextList extends InlineComponent { attributes.maxNumber = { createComponentOfType: "number", createStateVariable: "maxNumber", - defaultValue: null, + defaultValue: Infinity, public: true, }; + attributes.fixed = { + leaveRaw: true, + }; + + attributes.isResponse = { + leaveRaw: true, + }; + return attributes; } @@ -63,24 +78,12 @@ export default class TextList extends InlineComponent { group: "texts", componentTypes: ["text"], }, - { - group: "textLists", - componentTypes: ["textList"], - }, ]; } static returnStateVariableDefinitions() { let stateVariableDefinitions = super.returnStateVariableDefinitions(); - // set overrideChildHide so that children are hidden - // only based on whether or not the list is hidden - // so that can't have a list with partially hidden components - stateVariableDefinitions.overrideChildHide = { - returnDependencies: () => ({}), - definition: () => ({ setValue: { overrideChildHide: true } }), - }; - stateVariableDefinitions.textsShadow = { defaultValue: null, hasEssential: true, @@ -92,27 +95,28 @@ export default class TextList extends InlineComponent { }), }; + stateVariableDefinitions.asList = { + returnDependencies: () => ({}), + definition() { + return { setValue: { asList: true } }; + }, + }; + stateVariableDefinitions.numComponents = { public: true, shadowingInstructions: { createComponentOfType: "number", }, - additionalStateVariablesDefined: ["childIndexByArrayKey"], + additionalStateVariablesDefined: ["childNameByComponent"], returnDependencies() { return { maxNumber: { dependencyType: "stateVariable", variableName: "maxNumber", }, - textListChildren: { - dependencyType: "child", - childGroups: ["textLists"], - variableNames: ["numComponents"], - }, - textAndTextListChildren: { + textChildren: { dependencyType: "child", - childGroups: ["texts", "textLists"], - skipComponentNames: true, + childGroups: ["texts"], }, textsShadow: { dependencyType: "stateVariable", @@ -120,70 +124,42 @@ export default class TextList extends InlineComponent { }, }; }, - definition: function ({ dependencyValues, componentInfoObjects }) { + definition: function ({ dependencyValues }) { let numComponents = 0; - let childIndexByArrayKey = []; - - if (dependencyValues.textAndTextListChildren.length > 0) { - let nTextLists = 0; - for (let [ - childInd, - child, - ] of dependencyValues.textAndTextListChildren.entries()) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "textList", - }) - ) { - let textListChild = - dependencyValues.textListChildren[nTextLists]; - nTextLists++; - for ( - let i = 0; - i < textListChild.stateValues.numComponents; - i++ - ) { - childIndexByArrayKey[numComponents + i] = [ - childInd, - i, - ]; - } - numComponents += - textListChild.stateValues.numComponents; - } else { - childIndexByArrayKey[numComponents] = [childInd, 0]; - numComponents += 1; - } - } + let childNameByComponent = []; + + if (dependencyValues.textChildren.length > 0) { + childNameByComponent = dependencyValues.textChildren.map( + (x) => x.componentName, + ); + numComponents = dependencyValues.textChildren.length; } else if (dependencyValues.textsShadow !== null) { numComponents = dependencyValues.textsShadow.length; } let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && numComponents > maxNum) { + if (numComponents > maxNum) { numComponents = maxNum; - childIndexByArrayKey = childIndexByArrayKey.slice( + childNameByComponent = childNameByComponent.slice( 0, maxNum, ); } return { - setValue: { numComponents, childIndexByArrayKey }, + setValue: { numComponents, childNameByComponent }, checkForActualChange: { numComponents: true }, }; }, }; stateVariableDefinitions.texts = { - public: true, shadowingInstructions: { createComponentOfType: "text", }, isArray: true, entryPrefixes: ["text"], - stateVariablesDeterminingDependencies: ["childIndexByArrayKey"], + stateVariablesDeterminingDependencies: ["childNameByComponent"], returnArraySizeDependencies: () => ({ numComponents: { dependencyType: "stateVariable", @@ -197,9 +173,9 @@ export default class TextList extends InlineComponent { returnArrayDependenciesByKey({ arrayKeys, stateValues }) { let dependenciesByKey = {}; let globalDependencies = { - childIndexByArrayKey: { + childNameByComponent: { dependencyType: "stateVariable", - variableName: "childIndexByArrayKey", + variableName: "childNameByComponent", }, textsShadow: { dependencyType: "stateVariable", @@ -209,20 +185,14 @@ export default class TextList extends InlineComponent { for (let arrayKey of arrayKeys) { let childIndices = []; - let textIndex = "1"; - if (stateValues.childIndexByArrayKey[arrayKey]) { - childIndices = [ - stateValues.childIndexByArrayKey[arrayKey][0], - ]; - textIndex = - stateValues.childIndexByArrayKey[arrayKey][1] + 1; + if (stateValues.childNameByComponent[arrayKey]) { + childIndices = [arrayKey]; } dependenciesByKey[arrayKey] = { - textAndTextListChildren: { + textChildren: { dependencyType: "child", - childGroups: ["texts", "textLists"], - variableNames: ["value", "text" + textIndex], - variablesOptional: true, + childGroups: ["texts"], + variableNames: ["value"], childIndices, }, }; @@ -238,21 +208,10 @@ export default class TextList extends InlineComponent { let texts = {}; for (let arrayKey of arrayKeys) { - let child = - dependencyValuesByKey[arrayKey] - .textAndTextListChildren[0]; + let child = dependencyValuesByKey[arrayKey].textChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - texts[arrayKey] = child.stateValues.value; - } else { - let textIndex = - globalDependencyValues.childIndexByArrayKey[ - arrayKey - ][1] + 1; - texts[arrayKey] = - child.stateValues["text" + textIndex]; - } + texts[arrayKey] = child.stateValues.value; } else if (globalDependencyValues.textsShadow !== null) { texts[arrayKey] = globalDependencyValues.textsShadow[arrayKey]; @@ -266,7 +225,7 @@ export default class TextList extends InlineComponent { globalDependencyValues, dependencyValuesByKey, dependencyNamesByKey, - arraySize, + workspace, }) { let instructions = []; @@ -275,32 +234,29 @@ export default class TextList extends InlineComponent { continue; } - let child = - dependencyValuesByKey[arrayKey] - .textAndTextListChildren[0]; + let child = dependencyValuesByKey[arrayKey].textChildren[0]; if (child) { - if (child.stateValues.value !== undefined) { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .textAndTextListChildren, - desiredValue: - desiredStateVariableValues.texts[arrayKey], - childIndex: 0, - variableIndex: 0, - }); - } else { - instructions.push({ - setDependency: - dependencyNamesByKey[arrayKey] - .textAndTextListChildren, - desiredValue: - desiredStateVariableValues.texts[arrayKey], - childIndex: 0, - variableIndex: 1, - }); + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey].textChildren, + desiredValue: + desiredStateVariableValues.texts[arrayKey], + childIndex: 0, + variableIndex: 0, + }); + } else if (globalDependencyValues.textsShadow !== null) { + if (!workspace.desiredTextShadow) { + workspace.desiredTextShadow = [ + ...globalDependencyValues.textsShadow, + ]; } + workspace.desiredTextShadow[arrayKey] = + desiredStateVariableValues.texts[arrayKey]; + instructions.push({ + setDependency: "textsShadow", + desiredValue: workspace.desiredTextShadow, + }); } } @@ -321,158 +277,188 @@ export default class TextList extends InlineComponent { targetVariableName: "texts", }; - stateVariableDefinitions.text = { - public: true, - shadowingInstructions: { - createComponentOfType: "text", - }, - forRenderer: true, + stateVariableDefinitions.readyToExpandWhenResolved = { returnDependencies: () => ({ - texts: { + childNameByComponent: { dependencyType: "stateVariable", - variableName: "texts", + variableName: "childNameByComponent", }, }), - definition: ({ dependencyValues }) => ({ - setValue: { text: dependencyValues.texts.join(", ") }, - }), + // When this state variable is marked stale + // it indicates we should update replacements. + // For this to work, must set + // stateVariableToEvaluateAfterReplacements + // to this variable so that it is marked fresh + markStale: () => ({ updateReplacements: true }), + definition: function () { + return { setValue: { readyToExpandWhenResolved: true } }; + }, }; - stateVariableDefinitions.componentNamesInList = { - returnDependencies: () => ({ - textAndTextListChildren: { - dependencyType: "child", - childGroups: ["texts", "textLists"], - variableNames: ["componentNamesInList"], - variablesOptional: true, - }, - maxNumber: { - dependencyType: "stateVariable", - variableName: "maxNumber", - }, - }), - definition: function ({ dependencyValues, componentInfoObjects }) { - let componentNamesInList = []; - - for (let child of dependencyValues.textAndTextListChildren) { - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "textList", - }) - ) { - componentNamesInList.push( - ...child.stateValues.componentNamesInList, - ); - } else { - componentNamesInList.push(child.componentName); - } - } + return stateVariableDefinitions; + } - let maxNum = dependencyValues.maxNumber; - if (maxNum !== null && componentNamesInList.length > maxNum) { - maxNum = Math.max(0, Math.floor(maxNum)); - componentNamesInList = componentNamesInList.slice( - 0, - maxNum, + static async createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }) { + let errors = []; + let warnings = []; + + let replacements = []; + let componentsCopied = []; + + let attributesToConvert = {}; + for (let attr of ["fixed", "isResponse"]) { + if (attr in component.attributes) { + attributesToConvert[attr] = component.attributes[attr]; + } + } + + let newNamespace = component.attributes.newNamespace?.primitive; + + // allow one to override the fixed and isResponse attributes + // as well as rounding settings + // by specifying it on the sequence + let attributesFromComposite = {}; + + if (Object.keys(attributesToConvert).length > 0) { + attributesFromComposite = convertAttributesForComponentType({ + attributes: attributesToConvert, + componentType: "text", + componentInfoObjects, + compositeCreatesNewNamespace: newNamespace, + }); + } + + let childNameByComponent = + await component.stateValues.childNameByComponent; + + if (childNameByComponent.length > 0) { + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; + + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); + + let repl = await replacementSource.serialize({ + primitiveSourceAttributesToIgnore: ["isResponse"], + }); + if (!repl.attributes) { + repl.attributes = {}; + } + Object.assign( + repl.attributes, + JSON.parse(JSON.stringify(attributesFromComposite)), ); + replacements.push(repl); } + } + } else { + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + replacements.push({ + componentType: "text", + attributes: JSON.parse( + JSON.stringify(attributesFromComposite), + ), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `text${i + 1}`, + }, + ], + }, + }); + } + } + + workspace.uniqueIdentifiersUsed = []; + replacements = postProcessCopy({ + serializedComponents: replacements, + componentName: component.componentName, + uniqueIdentifiersUsed: workspace.uniqueIdentifiersUsed, + addShadowDependencies: true, + markAsPrimaryShadow: true, + }); - return { setValue: { componentNamesInList } }; - }, - }; + let processResult = processAssignNames({ + assignNames: component.doenetAttributes.assignNames, + serializedComponents: replacements, + parentName: component.componentName, + parentCreatesNewNamespace: newNamespace, + componentInfoObjects, + }); + errors.push(...processResult.errors); + warnings.push(...processResult.warnings); - stateVariableDefinitions.numComponentsToDisplayByChild = { - additionalStateVariablesDefined: ["numChildrenToRender"], - returnDependencies: () => ({ - numComponents: { - dependencyType: "stateVariable", - variableName: "numComponents", - }, - textListChildren: { - dependencyType: "child", - childGroups: ["textLists"], - variableNames: ["numComponents"], - }, - textAndTextListChildren: { - dependencyType: "child", - childGroups: ["texts", "textLists"], - skipComponentNames: true, - }, - parentNComponentsToDisplayByChild: { - dependencyType: "parentStateVariable", - parentComponentType: "textList", - variableName: "numComponentsToDisplayByChild", - }, - }), - definition: function ({ - dependencyValues, - componentInfoObjects, - componentName, - }) { - let numComponentsToDisplay = dependencyValues.numComponents; - - if ( - dependencyValues.parentNComponentsToDisplayByChild !== null - ) { - // have a parent textList, which could have limited - // text of components to display - numComponentsToDisplay = - dependencyValues.parentNComponentsToDisplayByChild[ - componentName - ]; - } + workspace.componentsCopied = componentsCopied; - let numComponentsToDisplayByChild = {}; + return { + replacements: processResult.serializedComponents, + errors, + warnings, + }; + } - let numComponentsSoFar = 0; - let numChildrenToRender = 0; + static async calculateReplacementChanges({ + component, + components, + componentInfoObjects, + workspace, + }) { + // TODO: don't yet have a way to return errors and warnings! + let errors = []; + let warnings = []; + + let componentsToCopy = []; + + let childNameByComponent = + await component.stateValues.childNameByComponent; + + for (let childName of childNameByComponent) { + let replacementSource = components[childName]; + + if (replacementSource) { + componentsToCopy.push(replacementSource.componentName); + } + } + + if ( + componentsToCopy.length == workspace.componentsCopied.length && + workspace.componentsCopied.every( + (x, i) => x === componentsToCopy[i], + ) + ) { + return []; + } + + // for now, just recreate + let replacementResults = await this.createSerializedReplacements({ + component, + components, + componentInfoObjects, + workspace, + }); - let nTextLists = 0; - for (let child of dependencyValues.textAndTextListChildren) { - let numComponentsLeft = Math.max( - 0, - numComponentsToDisplay - numComponentsSoFar, - ); - if (numComponentsLeft > 0) { - numChildrenToRender++; - } - if ( - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "textList", - }) - ) { - let textListChild = - dependencyValues.textListChildren[nTextLists]; - nTextLists++; - - let numComponentsForTextListChild = Math.min( - numComponentsLeft, - textListChild.stateValues.numComponents, - ); - - numComponentsToDisplayByChild[ - textListChild.componentName - ] = numComponentsForTextListChild; - numComponentsSoFar += numComponentsForTextListChild; - } else { - numComponentsSoFar += 1; - } - } + let replacements = replacementResults.replacements; + errors.push(...replacementResults.errors); + warnings.push(...replacementResults.warnings); - return { - setValue: { - numComponentsToDisplayByChild, - numChildrenToRender, - }, - }; + let replacementChanges = [ + { + changeType: "add", + changeTopLevelReplacements: true, + firstReplacementInd: 0, + numberReplacementsToReplace: component.replacements.length, + serializedReplacements: replacements, }, - markStale: () => ({ updateRenderedChildren: true }), - }; + ]; - return stateVariableDefinitions; + return replacementChanges; } - - static adapters = ["text"]; } diff --git a/packages/doenetml-worker/src/components/When.js b/packages/doenetml-worker/src/components/When.js index 2617b4548..fbee1133d 100644 --- a/packages/doenetml-worker/src/components/When.js +++ b/packages/doenetml-worker/src/components/When.js @@ -126,34 +126,18 @@ export default class When extends BooleanComponent { dependencyType: "stateVariable", variableName: "booleanChildrenByCode", }, - booleanListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "booleanListChildrenByCode", - }, textChildrenByCode: { dependencyType: "stateVariable", variableName: "textChildrenByCode", }, - textListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "textListChildrenByCode", - }, mathChildrenByCode: { dependencyType: "stateVariable", variableName: "mathChildrenByCode", }, - mathListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "mathListChildrenByCode", - }, numberChildrenByCode: { dependencyType: "stateVariable", variableName: "numberChildrenByCode", }, - numberListChildrenByCode: { - dependencyType: "stateVariable", - variableName: "numberListChildrenByCode", - }, otherChildrenByCode: { dependencyType: "stateVariable", variableName: "otherChildrenByCode", diff --git a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts index 14dc429c4..a8a3cdc26 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts @@ -880,13 +880,15 @@ async function test_answer_multiple_inputs({ awardsUsed?: string[]; }[]; answerName?: string; - inputs: { type: "math" | "text" | "boolean"; name?: string }[]; + inputs: { type: "math" | "number" | "text" | "boolean"; name?: string }[]; }) { let fromLatexBase = getLatexToMathConverter(); let fromLatex = (x: string) => fromLatexBase(normalizeLatexString(x)); let currentResponses = inputs.map((input) => { if (input.type === "math") { return "\uff3f"; + } else if (input.type === "number") { + return NaN; } else if (input.type === "text") { return ""; } else { @@ -914,7 +916,10 @@ async function test_answer_multiple_inputs({ function transformOutputValues(values: any[]) { return values.map((val, i) => { - if (inputs[i].type === "math") { + if ( + (inputs[i].type === "math" || inputs[i].type === "number") && + val.tree !== undefined + ) { val = val.tree; } return val; @@ -925,6 +930,8 @@ async function test_answer_multiple_inputs({ return values.map((val, i) => { if (inputs[i].type === "math") { val = fromLatex(val).tree; + } else if (inputs[i].type === "number") { + val = fromLatex(val).evaluate_to_constant(); } else if (inputs[i].type === "boolean") { val = val === "true"; } @@ -946,11 +953,15 @@ async function test_answer_multiple_inputs({ inputs[i].type === "math" ? x.tree : x, ), ).eqls(submittedResponses); - expect( - transformOutputValues( - inputNames.map((name) => stateVariables[name].stateValues.value), - ), - ).eqls(currentResponses); + if (inputs.every((x) => x.type !== "number")) { + expect( + transformOutputValues( + inputNames.map( + (name) => stateVariables[name].stateValues.value, + ), + ), + ).eqls(currentResponses); + } for (let response of answers) { if (response.preAction) { @@ -977,7 +988,7 @@ async function test_answer_multiple_inputs({ // Type answers in for (let [ind, input] of inputs.entries()) { - if (input.type === "math") { + if (input.type === "math" || input.type === "number") { await updateMathInputValue({ latex: values[ind], componentName: inputNames[ind], @@ -1344,6 +1355,20 @@ describe("Answer tag tests", async () => { }); }); + it("answer sugar from one string, set to boolean 2", async () => { + const doenetML = ` + not false + `; + + await test_boolean_answer({ + doenetML, + answers: [ + { boolean: true, credit: 1 }, + { boolean: false, credit: 0 }, + ], + }); + }); + it("answer sugar from one boolean", async () => { const doenetML = ` true @@ -1768,8 +1793,8 @@ describe("Answer tag tests", async () => { { values: ["2", "1"], credit: 0.5 }, ], inputs: [ - { type: "math", name: "/mi1" }, - { type: "math", name: "/mi2" }, + { type: "number", name: "/mi1" }, + { type: "number", name: "/mi2" }, ], }); }); @@ -1997,6 +2022,20 @@ describe("Answer tag tests", async () => { }); }); + it("answer set to boolean, award with sugared string", async () => { + const doenetML = ` + not false + `; + + await test_boolean_answer({ + doenetML, + answers: [ + { boolean: true, credit: 1 }, + { boolean: false, credit: 0 }, + ], + }); + }); + it("answer award with sugared boolean and string", async () => { const doenetML = ` false @@ -2012,6 +2051,56 @@ describe("Answer tag tests", async () => { }); }); + it("answer from booleanList", async () => { + const doenetML = ` + + + $bi1 $bi2= false true + + + `; + + await test_answer_multiple_inputs({ + doenetML, + answers: [ + { values: ["false", "true"], credit: 1 }, + { values: ["false", "false"], credit: 0.5 }, + { values: ["true", "false"], credit: 0 }, + { values: ["true", "true"], credit: 0.5 }, + ], + inputs: [ + { type: "boolean", name: "/bi1" }, + { type: "boolean", name: "/bi2" }, + ], + }); + }); + + it("answer with multiple booleans", async () => { + const doenetML = ` + + + false + + $bi1{isResponse} = not $b and $bi2{isResponse}=$b + + + `; + + await test_answer_multiple_inputs({ + doenetML, + answers: [ + { values: ["true", "false"], credit: 1 }, + { values: ["false", "true"], credit: 0 }, + { values: ["false", "false"], credit: 0 }, + { values: ["true", "true"], credit: 0 }, + ], + inputs: [ + { type: "boolean", name: "/bi1" }, + { type: "boolean", name: "/bi2" }, + ], + }); + }); + it("answer multiple shortcut awards", async () => { const doenetML = ` x+yx @@ -3718,6 +3807,60 @@ Enter any letter: }); }); + it("default is to split symbols, sugared answer", async () => { + const doenetML = ` +xyz + `; + await test_math_answer({ + doenetML, + answers: [ + { + latex: "xyza", + credit: 0, + }, + { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, + ], + }); + }); + + it("default is to split symbols, shortcut award, sugared math", async () => { + const doenetML = ` +xyz + `; + await test_math_answer({ + doenetML, + answers: [ + { + latex: "xyza", + credit: 0, + }, + { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, + ], + }); + }); + + it("default is to split symbols, explicit mathInput and math", async () => { + const doenetML = ` + + + xyz + + `; + await test_math_answer({ + doenetML, + answers: [ + { + latex: "xyza", + credit: 0, + }, + { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, + ], + }); + }); + it("with split symbols, specified directly on mathInput and math", async () => { const doenetML = `

split symbols:

@@ -3731,6 +3874,7 @@ Enter any letter: doenetML, answers: [ { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, { latex: "xyza", credit: 0, @@ -3741,6 +3885,7 @@ Enter any letter: }, }, { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, { latex: "xyzb", credit: 0, @@ -3752,6 +3897,7 @@ Enter any letter: overrideResponse: "xyzb", }, { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, ], }); }); @@ -3765,6 +3911,7 @@ Enter any letter: doenetML, answers: [ { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, { latex: "xyza", credit: 0, @@ -3775,6 +3922,7 @@ Enter any letter: }, }, { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, { latex: "xyzb", credit: 0, @@ -3786,6 +3934,7 @@ Enter any letter: overrideResponse: "xyzb", }, { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, ], }); }); @@ -3801,6 +3950,7 @@ Enter any letter: doenetML, answers: [ { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, { latex: "xyza", credit: 0, @@ -3811,6 +3961,7 @@ Enter any letter: }, }, { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, { latex: "xyzb", credit: 0, @@ -3822,6 +3973,7 @@ Enter any letter: overrideResponse: "xyzb", }, { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, ], }); }); @@ -3838,6 +3990,7 @@ Enter any letter: doenetML, answers: [ { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, { latex: "xyza", credit: 0, @@ -3848,6 +4001,7 @@ Enter any letter: }, }, { latex: "xyz", credit: 1 }, + { latex: "x y z", credit: 1 }, { latex: "xyzb", credit: 0, @@ -3859,6 +4013,7 @@ Enter any letter: overrideResponse: "xyzb", }, { latex: "xyz", credit: 1, overrideResponse: "xyz" }, + { latex: "x y z", credit: 0 }, ], }); }); @@ -3965,7 +4120,7 @@ Enter any letter: expect(stateVariables["/ans"].stateValues.creditAchieved).eq(1); }); - it("empty mathLists always equal", async () => { + it.skip("empty mathLists always equal", async () => { let core = await createTestCore({ doenetML: ` @@ -4887,7 +5042,7 @@ What is the derivative of x^2? }); }); - it("case-insensitive match, text", async () => { + it("case-insensitive match, math", async () => { await test_math_answer({ doenetML: `x+Y`, answers: [ @@ -4946,7 +5101,7 @@ What is the derivative of x^2? }); }); - it("case-insensitive match, math", async () => { + it("case-insensitive match, text", async () => { await test_text_answer({ doenetML: `Hello there!`, answers: [ diff --git a/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts b/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts index a13789eaf..549a14acd 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts @@ -226,7 +226,7 @@ describe("Boolean tag tests", async () => { expect(stateVariables[`/b4`].stateValues.value).to.be.true; }); - it("boolean with lists", async () => { + it("boolean with lists and sequences", async () => { let core = await createTestCore({ doenetML: ` 1,2 = 1 2 @@ -239,17 +239,20 @@ describe("Boolean tag tests", async () => { 1 2 = 2 1 a b = b a true false = false true - = - = - = - = - 1 = 1 - 1 = 1 - 1 = 1 - 1 = 1 - a, b = a b - a, b = b a - a,b = a b + 1 = 1 + 1 = 1 + 1 = 1 + 1 = 1 + a, b = a b + a, b = b a + a,b = a b + 1 2 = + 1 2 = + 1, 2 = + d f = + 2x 3x = + + 1,2 = 2 1 1 2 = 2 1 @@ -270,12 +273,18 @@ describe("Boolean tag tests", async () => { = = a, b = b a + 12 = + (1, 2) = 1 2 + (1, 2) = 1 2 + 1, 2 = 1 2 + d e f = + 2x 3x = `, }); - let nTrues = 21, - nFalses = 19; + let nTrues = 22, + nFalses = 25; let stateVariables = await returnAllStateVariables(core); for (let i = 1; i <= nTrues; i++) { @@ -292,7 +301,7 @@ describe("Boolean tag tests", async () => { } }); - it("element of list, set, or string", async () => { + it("element of list, set, composite, or string", async () => { let elements = [ { element: "1", @@ -324,6 +333,12 @@ describe("Boolean tag tests", async () => { isElement: true, isElementCaseInsensitive: true, }, + { + element: "1", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, { element: "1", set: "1 2", @@ -354,6 +369,12 @@ describe("Boolean tag tests", async () => { isElement: true, isElementCaseInsensitive: true, }, + { + element: "1", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, { element: "1", set: "1 2", @@ -384,6 +405,18 @@ describe("Boolean tag tests", async () => { isElement: true, isElementCaseInsensitive: true, }, + { + element: "1", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, + { + element: "1", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, { element: "3", set: "1 2", @@ -416,10 +449,27 @@ describe("Boolean tag tests", async () => { }, { element: "3", - set: "3", + set: "", isElement: false, isElementCaseInsensitive: false, - isInvalid: true, + }, + { + element: "3", + set: "3", + isElement: true, + isElementCaseInsensitive: true, + }, + { + element: "3", + set: "3", + isElement: true, + isElementCaseInsensitive: true, + }, + { + element: "3", + set: "3", + isElement: true, + isElementCaseInsensitive: true, }, { element: "2, 3", @@ -512,6 +562,12 @@ describe("Boolean tag tests", async () => { isElement: true, isElementCaseInsensitive: true, }, + { + element: "2x", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, { element: "2x", set: "x+x y/2", @@ -536,6 +592,18 @@ describe("Boolean tag tests", async () => { isElement: true, isElementCaseInsensitive: true, }, + { + element: "2x", + set: "", + isElement: true, + isElementCaseInsensitive: true, + }, + { + element: "2x", + set: "x+x", + isElement: true, + isElementCaseInsensitive: true, + }, { element: "2x", set: "x+X y/2", @@ -558,8 +626,7 @@ describe("Boolean tag tests", async () => { element: "2x", set: "x+X", isElement: false, - isElementCaseInsensitive: false, - isInvalid: true, + isElementCaseInsensitive: true, }, { element: "x", @@ -584,7 +651,6 @@ describe("Boolean tag tests", async () => { set: "abc", isElement: false, isElementCaseInsensitive: false, - isInvalid: true, }, { element: "b", @@ -625,8 +691,8 @@ describe("Boolean tag tests", async () => { { element: "b", set: "abc", - isElement: false, - isElementCaseInsensitive: false, + isElement: true, + isElementCaseInsensitive: true, }, { element: "b", @@ -760,20 +826,18 @@ describe("Boolean tag tests", async () => { { set1: "z", set2: "z 2x", - isSubset: false, - isSubsetCaseInsensitive: false, + isSubset: true, + isSubsetCaseInsensitive: true, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, { set1: "z", set2: "z 2x", - isSubset: false, - isSubsetCaseInsensitive: false, + isSubset: true, + isSubsetCaseInsensitive: true, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, { set1: "z", @@ -826,11 +890,10 @@ describe("Boolean tag tests", async () => { { set1: "3", set2: "[2,4)", - isSubset: false, - isSubsetCaseInsensitive: false, + isSubset: true, + isSubsetCaseInsensitive: true, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, { set1: "2,3", @@ -900,20 +963,98 @@ describe("Boolean tag tests", async () => { { set1: "hello", set2: "there hello bye", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "hello", + set2: "there hello bye", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "hello there", + set2: "there hello bye", isSubset: false, isSubsetCaseInsensitive: false, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, { - set1: "hello", + set1: "hello there", set2: "there hello bye", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "a c", + set2: "", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "a, c", + set2: "", isSubset: false, isSubsetCaseInsensitive: false, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, + }, + { + set1: "a, c", + set2: "", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "ace", + set2: "", + isSubset: false, + isSubsetCaseInsensitive: false, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "ace", + set2: "", + isSubset: true, + isSubsetCaseInsensitive: true, + isSuperset: true, + isSupersetCaseInsensitive: true, + }, + { + set1: "A c", + set2: "", + isSubset: false, + isSubsetCaseInsensitive: true, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "a b", + set2: "", + isSubset: false, + isSubsetCaseInsensitive: false, + isSuperset: false, + isSupersetCaseInsensitive: false, + }, + { + set1: "a b c", + set2: "", + isSubset: false, + isSubsetCaseInsensitive: false, + isSuperset: true, + isSupersetCaseInsensitive: true, }, { set1: "true true", @@ -942,20 +1083,18 @@ describe("Boolean tag tests", async () => { { set1: "true", set2: "true false", - isSubset: false, - isSubsetCaseInsensitive: false, + isSubset: true, + isSubsetCaseInsensitive: true, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, { set1: "true", set2: "true false", - isSubset: false, - isSubsetCaseInsensitive: false, + isSubset: true, + isSubsetCaseInsensitive: true, isSuperset: false, isSupersetCaseInsensitive: false, - isInvalid: true, }, ]; diff --git a/packages/doenetml-worker/src/test/tagSpecific/graph.test.ts b/packages/doenetml-worker/src/test/tagSpecific/graph.test.ts index c7733b598..d6f25553e 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/graph.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/graph.test.ts @@ -589,6 +589,32 @@ describe("Graph tag tests", async () => { ]); }); + it("correctly shadow references to number list grid", async () => { + let core = await createTestCore({ + doenetML: ` +
+ + +

Grid: $g.grid{name="cgr" assignNames="gr"}

+

Grid2: $cgr{assignNames="gr2a gr2b"}

+

Grid3: $gr{name="gr3"}

+
+ +
+ `, + }); + + let stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/gr"].stateValues.numbers).eqls([1, 2]); + expect(stateVariables["/gr2a"].stateValues.value).eq(1); + expect(stateVariables["/gr2b"].stateValues.value).eq(2); + expect(stateVariables["/gr3"].stateValues.numbers).eqls([1, 2]); + expect(stateVariables["/sec2/gr"].stateValues.numbers).eqls([1, 2]); + expect(stateVariables["/sec2/gr2a"].stateValues.value).eq(1); + expect(stateVariables["/sec2/gr2b"].stateValues.value).eq(2); + expect(stateVariables["/sec2/gr3"].stateValues.numbers).eqls([1, 2]); + }); + // check for bug in placeholder adapter it("graph with label as submitted response, createComponentOfType specified", async () => { let core = await createTestCore({ diff --git a/packages/doenetml-worker/src/test/tagSpecific/mathinput.test.ts b/packages/doenetml-worker/src/test/tagSpecific/mathinput.test.ts index 1dbba0e81..c28092120 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/mathinput.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/mathinput.test.ts @@ -7,6 +7,7 @@ import { updateMathInputValue, updateMathInputValueToImmediateValue, } from "../utils/actions"; +import me from "math-expressions"; const Mock = vi.fn(); vi.stubGlobal("postMessage", Mock); @@ -9372,4 +9373,267 @@ describe("MathInput tag tests", async () => { expect(stateVariables["/P"].stateValues.xs[0].tree).eq(5); expect(stateVariables["/P"].stateValues.xs[1].tree).eq(6); }); + + it("vector and matrix components", async () => { + let core = await createTestCore({ + doenetML: ` +

Value:

+

Index:

+ +

Number of dimensions: $mi.numDimensions

+

x: $mi.x

+

y: $mi.y

+

z: $mi.z

+

x1: $mi.x1

+

x2: $mi.x2

+

x3: $mi.x3

+

x4: $mi.x4

+

v: $mi.vector

+

v[$i]: $mi.vector[$i]

+

Matrix size: $mi.matrixSize

+

Number of rows: $mi.numRows

+

Number of columns: $mi.numColumns

+

Matrix: $mi.matrix

+

Matrix[$i]: $mi.matrix[$i]

+

Matrix[$i][1]: $mi.matrix[$i][1]

+ + `, + }); + + async function check_items(math: any, ind: number) { + const stateVariables = await returnAllStateVariables(core); + let mathTree = math.tree; + + let numDimensions = ["vector", "list"].includes(mathTree[0]) + ? mathTree.length - 1 + : 1; + let x1, x2, x3, x4; + try { + x1 = math.get_component(0); + } catch (e) { + x1 = math; + } + try { + x2 = math.get_component(1); + } catch (e) { + x2 = null; + } + try { + x3 = math.get_component(2); + } catch (e) { + x3 = null; + } + try { + x4 = math.get_component(3); + } catch (e) { + x4 = null; + } + + let asVec = math; + if (mathTree[0] === "list") { + asVec = me.fromAst(["vector", ...mathTree.slice(1)]); + } + + expect(stateVariables["/p1"].stateValues.text).eq( + `Number of dimensions: ${numDimensions}`, + ); + expect(stateVariables["/mi"].stateValues.numDimensions).eq( + numDimensions, + ); + + expect(stateVariables["/p2"].stateValues.text).eq(`x: ${x1}`); + expect(stateVariables["/p5"].stateValues.text).eq(`x1: ${x1}`); + expect(stateVariables["/mi"].stateValues.x1.tree).eqls(x1.tree); + + if (x2) { + expect(stateVariables["/p3"].stateValues.text).eq(`y: ${x2}`); + expect(stateVariables["/p6"].stateValues.text).eq(`x2: ${x2}`); + expect(stateVariables["/mi"].stateValues.x2.tree).eqls(x2.tree); + } else { + expect(stateVariables["/p3"].stateValues.text).eq(`y: `); + expect(stateVariables["/p6"].stateValues.text).eq(`x2: `); + expect(stateVariables["/mi"].stateValues.x2).eq(undefined); + } + + if (x3) { + expect(stateVariables["/p4"].stateValues.text).eq(`z: ${x3}`); + expect(stateVariables["/p7"].stateValues.text).eq(`x3: ${x3}`); + expect(stateVariables["/mi"].stateValues.x3.tree).eqls(x3.tree); + } else { + expect(stateVariables["/p4"].stateValues.text).eq(`z: `); + expect(stateVariables["/p7"].stateValues.text).eq(`x3: `); + expect(stateVariables["/mi"].stateValues.x3).eq(undefined); + } + + if (x4) { + expect(stateVariables["/p8"].stateValues.text).eq(`x4: ${x4}`); + expect(stateVariables["/mi"].stateValues.x4.tree).eqls(x4.tree); + } else { + expect(stateVariables["/p8"].stateValues.text).eq(`x4: `); + expect(stateVariables["/mi"].stateValues.x4).eq(undefined); + } + + expect(stateVariables["/p9"].stateValues.text).eq(`v: ${asVec}`); + if (numDimensions === 1) { + expect( + stateVariables["/mi"].stateValues.vector.map((v) => v.tree), + ).eqls([math.tree]); + } else { + expect( + stateVariables["/mi"].stateValues.vector.map((v) => v.tree), + ).eqls(math.tree.slice(1)); + } + + if (i === 1) { + expect(stateVariables["/p10"].stateValues.text).eq( + `v[${i}]: ${x1}`, + ); + } else if (i <= numDimensions) { + expect(stateVariables["/p10"].stateValues.text).eq( + `v[${i}]: ${math.get_component(i - 1)}`, + ); + } else { + expect(stateVariables["/p10"].stateValues.text).eq(`v[${i}]: `); + } + + expect(stateVariables["/p11"].stateValues.text).eq( + `Matrix size: ${numDimensions}, 1`, + ); + expect(stateVariables["/mi"].stateValues.matrixSize).eqls([ + numDimensions, + 1, + ]); + expect(stateVariables["/p12"].stateValues.text).eq( + `Number of rows: ${numDimensions}`, + ); + expect(stateVariables["/mi"].stateValues.numRows).eq(numDimensions); + expect(stateVariables["/p13"].stateValues.text).eq( + `Number of columns: 1`, + ); + expect(stateVariables["/mi"].stateValues.numColumns).eq(1); + if (numDimensions === 1) { + expect(stateVariables["/p14"].stateValues.text).eq( + `Matrix: [ [ ${math} ] ]`, + ); + expect( + stateVariables["/mi"].stateValues.matrix.map((v) => + v.map((x) => x.tree), + ), + ).eqls([[math.tree]]); + } else { + expect(stateVariables["/p14"].stateValues.text).eq( + `Matrix: [ ${[...Array(numDimensions).keys()].map((i) => `[ ${math.get_component(i)} ]`).join(", ")} ]`, + ); + expect( + stateVariables["/mi"].stateValues.matrix.map((v) => + v.map((x) => x.tree), + ), + ).eqls(math.tree.slice(1).map((v) => [v])); + } + + if (i === 1) { + expect(stateVariables["/p15"].stateValues.text).eq( + `Matrix[${i}]: [ [ ${x1} ] ]`, + ); + } else if (i <= numDimensions) { + expect(stateVariables["/p15"].stateValues.text).eq( + `Matrix[${i}]: [ [ ${math.get_component(i - 1)} ] ]`, + ); + } else { + expect(stateVariables["/p15"].stateValues.text).eq( + `Matrix[${i}]: `, + ); + } + + if (i === 1) { + expect(stateVariables["/p16"].stateValues.text).eq( + `Matrix[${i}][1]: ${x1}`, + ); + } else if (i <= numDimensions) { + expect(stateVariables["/p16"].stateValues.text).eq( + `Matrix[${i}][1]: ${math.get_component(i - 1)}`, + ); + } else { + expect(stateVariables["/p16"].stateValues.text).eq( + `Matrix[${i}][1]: `, + ); + } + } + + let math = me.fromAst("\uff3f"); + let i = 1; + await check_items(math, i); + + await updateMathInputValue({ + latex: "(1,2)", + componentName: "/mi", + core, + }); + math = me.fromAst(["vector", 1, 2]); + await check_items(math, i); + + i = 2; + await updateMathInputValue({ + latex: i.toString(), + componentName: "/i", + core, + }); + await check_items(math, i); + + i = 3; + await updateMathInputValue({ + latex: i.toString(), + componentName: "/i", + core, + }); + await check_items(math, i); + + await updateMathInputValue({ + latex: "(a,b,c)", + componentName: "/mi", + core, + }); + math = me.fromAst(["vector", "a", "b", "c"]); + await check_items(math, i); + + i = 4; + await updateMathInputValue({ + latex: i.toString(), + componentName: "/i", + core, + }); + await check_items(math, i); + + i = 2; + await updateMathInputValue({ + latex: i.toString(), + componentName: "/i", + core, + }); + await check_items(math, i); + + await updateMathInputValue({ + latex: "xyz", + componentName: "/mi", + core, + }); + math = me.fromAst(["*", "x", "y", "z"]); + await check_items(math, i); + + i = 1; + await updateMathInputValue({ + latex: i.toString(), + componentName: "/i", + core, + }); + await check_items(math, i); + + await updateMathInputValue({ + latex: "p,q", + componentName: "/mi", + core, + }); + math = me.fromAst(["list", "p", "q"]); + await check_items(math, i); + }); }); diff --git a/packages/doenetml-worker/src/test/tagSpecific/text.test.ts b/packages/doenetml-worker/src/test/tagSpecific/text.test.ts index b77f783b3..d21faae80 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/text.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/text.test.ts @@ -919,12 +919,13 @@ describe("Text tag tests", async () => { ); }); - it("numCharacters", async () => { + it("numCharacters and characters", async () => { let core = await createTestCore({ doenetML: `

Hello there!

Number of characters is $t.numCharacters.

+

Characters: $t.characters.

`, }); @@ -933,5 +934,76 @@ describe("Text tag tests", async () => { expect(stateVariables["/p2"].stateValues.text).eq( "Number of characters is 11.", ); + expect(stateVariables["/p3"].stateValues.text).eq( + "Characters: H, e, l, l, o, , t, h, e, r, e.", + ); + + expect(stateVariables["/t"].stateValues.numCharacters).eq(11); + expect(stateVariables["/t"].stateValues.characters).eqls([ + "H", + "e", + "l", + "l", + "o", + " ", + "t", + "h", + "e", + "r", + "e", + ]); + }); + + it("numWords and words", async () => { + let core = await createTestCore({ + doenetML: ` +

Hello there now!

+ +

Number of words is $t.numWords.

+

words: $t.words.

+ `, + }); + + let stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + "Number of words is 3.", + ); + expect(stateVariables["/p3"].stateValues.text).eq( + "words: Hello, there, now.", + ); + + expect(stateVariables["/t"].stateValues.numWords).eq(3); + expect(stateVariables["/t"].stateValues.words).eqls([ + "Hello", + "there", + "now", + ]); + }); + + it("numListItems and listItems", async () => { + let core = await createTestCore({ + doenetML: ` +

Hello there, friend!!

+ +

Number of list items is $t.numListItems.

+

list items: $t.listItems.

+ `, + }); + + let stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + "Number of list items is 2.", + ); + expect(stateVariables["/p3"].stateValues.text).eq( + "list items: Hello there, friend!.", + ); + + expect(stateVariables["/t"].stateValues.numListItems).eq(2); + expect(stateVariables["/t"].stateValues.listItems).eqls([ + "Hello there", + "friend!", + ]); }); }); diff --git a/packages/doenetml-worker/src/test/tagSpecific/textinput.test.ts b/packages/doenetml-worker/src/test/tagSpecific/textinput.test.ts index 2f81e6ab0..c4f21b5ee 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/textinput.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/textinput.test.ts @@ -1574,4 +1574,92 @@ describe("TextInput tag tests", async () => { "Hello \\(\\frac{a}{b}\\)", ); }); + + it("characters, words, and list items", async () => { + let core = await createTestCore({ + doenetML: ` +

+ +

Number of characters is $ti.numCharacters.

+

Characters: $ti.characters.

+

Number of words is $ti.numWords.

+

Words: $ti.words.

+

Number of list items is $ti.numListItems.

+

List items: $ti.listItems.

+ `, + }); + + async function check_items(string: string) { + //@ts-ignore + const itr = new Intl.Segmenter("en", { + granularity: "grapheme", + }).segment(string); + + const characters = Array.from(itr, ({ segment }) => segment); + const numCharacters = characters.length; + + const words = string.trim().split(/\s+/); + const numWords = words.length; + + const listItems = string + .trim() + .split(",") + .map((s) => s.trim()); + const numListItems = listItems.length; + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + `Number of characters is ${numCharacters}.`, + ); + expect(stateVariables["/p3"].stateValues.text).eq( + `Characters: ${characters.map((v) => v.trim()).join(", ")}.`, + ); + expect(stateVariables["/p4"].stateValues.text).eq( + `Number of words is ${numWords}.`, + ); + expect(stateVariables["/p5"].stateValues.text).eq( + `Words: ${words.join(", ")}.`, + ); + expect(stateVariables["/p6"].stateValues.text).eq( + `Number of list items is ${numListItems}.`, + ); + expect(stateVariables["/p7"].stateValues.text).eq( + `List items: ${listItems.join(", ")}.`, + ); + + expect(stateVariables["/ti"].stateValues.numCharacters).eq( + numCharacters, + ); + expect(stateVariables["/ti"].stateValues.characters).eqls( + characters, + ); + expect(stateVariables["/ti"].stateValues.numWords).eq(numWords); + expect(stateVariables["/ti"].stateValues.words).eqls(words); + expect(stateVariables["/ti"].stateValues.numListItems).eq( + numListItems, + ); + expect(stateVariables["/ti"].stateValues.listItems).eqls(listItems); + } + + let string = ""; + await check_items(string); + + string = "Rainbow room"; + + await updateTextInputValue({ + text: string, + componentName: "/ti", + core, + }); + await check_items(string); + + string = "black cat, green goblin,great big red dog"; + await updateTextInputValue({ + text: string, + componentName: "/ti", + core, + }); + await check_items(string); + }); }); diff --git a/packages/doenetml-worker/src/utils/booleanLogic.js b/packages/doenetml-worker/src/utils/booleanLogic.js index 9c087ac79..a3d9bdbd7 100644 --- a/packages/doenetml-worker/src/utils/booleanLogic.js +++ b/packages/doenetml-worker/src/utils/booleanLogic.js @@ -6,6 +6,7 @@ import { getTextToMathConverter, numberToMathExpression, } from "./math"; +import { createInputStringFromChildren } from "./parseMath"; const appliedFunctionSymbolsWithBooleanOperators = [ ...appliedFunctionSymbolsDefault, @@ -26,6 +27,8 @@ var fromTextSplit = getTextToMathConverter({ export function buildParsedExpression({ dependencyValues, componentInfoObjects, + doNotSplit = false, + splitAtInitialLevel = false, }) { let codePre = "comp"; @@ -44,44 +47,32 @@ export function buildParsedExpression({ } } - let inputString = ""; + let stringResults = createInputStringFromChildren({ + children: dependencyValues.allChildren, + codePre, + format: "text", + createInternalLists: true, + parser: fromTextUnsplit, + }); + + let inputString = stringResults.string; + let internalLists = stringResults.internalLists; + let subnum = 0; let nonMathCodes = []; - let stringChildInd = 0; for (let child of dependencyValues.allChildren) { - if (typeof child === "string") { - // need to use stringChildren - // as child variable doesn't have stateVariables - inputString += - " " + dependencyValues.stringChildren[stringChildInd] + " "; - stringChildInd++; - } else { - // a math, mathList, number, numberList, text, textList, boolean, or booleanList + if (typeof child !== "string") { let code = codePre + subnum; - - // make sure code is surrounded by spaces - // (the presence of numbers inside code will ensure that - // it is parsed as a multicharcter variable) - inputString += " " + code + " "; - if ( !( componentInfoObjects.isInheritedComponentType({ inheritedComponentType: child.componentType, baseComponentType: "math", }) || - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "mathList", - }) || componentInfoObjects.isInheritedComponentType({ inheritedComponentType: child.componentType, baseComponentType: "number", - }) || - componentInfoObjects.isInheritedComponentType({ - inheritedComponentType: child.componentType, - baseComponentType: "numberList", }) ) ) { @@ -99,12 +90,16 @@ export function buildParsedExpression({ } catch (e) {} if (parsedExpression) { - parsedExpression = me.fromAst( - splitSymbolsIfMath({ - logicTree: parsedExpression.tree, - nonMathCodes, - }), - ); + parsedExpression = parsedExpression.substitute(internalLists); + if (!doNotSplit) { + parsedExpression = me.fromAst( + splitSymbolsIfMath({ + logicTree: parsedExpression.tree, + nonMathCodes, + init: !splitAtInitialLevel, + }), + ); + } } return { @@ -115,6 +110,96 @@ export function buildParsedExpression({ }; } +export function returnChildrenByCodeStateVariableDefinitions() { + let stateVariableDefinitions = {}; + + stateVariableDefinitions.mathChildrenByCode = { + additionalStateVariablesDefined: [ + "numberChildrenByCode", + "textChildrenByCode", + "booleanChildrenByCode", + "otherChildrenByCode", + ], + returnDependencies: () => ({ + children: { + dependencyType: "child", + childGroups: ["comparableTypes"], + variableNames: [ + "value", + "fractionSatisfied", + "unordered", + "inUnorderedList", + ], + variablesOptional: true, + }, + codePre: { + dependencyType: "stateVariable", + variableName: "codePre", + }, + }), + definition({ dependencyValues, componentInfoObjects }) { + let mathChildrenByCode = {}; + let numberChildrenByCode = {}; + let textChildrenByCode = {}; + let booleanChildrenByCode = {}; + let otherChildrenByCode = {}; + let subnum = 0; + + let codePre = dependencyValues.codePre; + + for (let child of dependencyValues.children) { + // a math, text, or boolean + let code = codePre + subnum; + + if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: child.componentType, + baseComponentType: "math", + }) + ) { + mathChildrenByCode[code] = child; + } else if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: child.componentType, + baseComponentType: "number", + }) + ) { + numberChildrenByCode[code] = child; + } else if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: child.componentType, + baseComponentType: "text", + }) + ) { + textChildrenByCode[code] = child; + } else if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: child.componentType, + baseComponentType: "boolean", + }) + ) { + booleanChildrenByCode[code] = child; + } else { + otherChildrenByCode[code] = child; + } + subnum += 1; + } + + return { + setValue: { + mathChildrenByCode, + numberChildrenByCode, + textChildrenByCode, + booleanChildrenByCode, + otherChildrenByCode, + }, + }; + }, + }; + + return stateVariableDefinitions; +} + export function evaluateLogic({ logicTree, canOverrideUnorderedCompare = false, @@ -226,30 +311,28 @@ export function evaluateLogic({ let foundBoolean = false; let foundOther = false; - operands.forEach(function (x) { - if (typeof x === "string") { - if ( - x in dependencyValues.mathChildrenByCode || - x in dependencyValues.mathListChildrenByCode || - x in dependencyValues.numberChildrenByCode || - x in dependencyValues.numberListChildrenByCode - ) { - foundMath = true; - } else if ( - x in dependencyValues.textChildrenByCode || - x in dependencyValues.textListChildrenByCode - ) { - foundText = true; - } else if ( - x in dependencyValues.booleanChildrenByCode || - x in dependencyValues.booleanListChildrenByCode - ) { - foundBoolean = true; - } else if (x in dependencyValues.otherChildrenByCode) { - foundOther = true; + function findTypes(ops) { + for (let op of ops) { + if (typeof op === "string") { + if ( + op in dependencyValues.mathChildrenByCode || + op in dependencyValues.numberChildrenByCode + ) { + foundMath = true; + } else if (op in dependencyValues.textChildrenByCode) { + foundText = true; + } else if (op in dependencyValues.booleanChildrenByCode) { + foundBoolean = true; + } else if (op in dependencyValues.otherChildrenByCode) { + foundOther = true; + } + } else if (Array.isArray(op)) { + findTypes(op.slice(1)); } } - }); + } + + findTypes(operands); let replaceMath = function (tree) { if (typeof tree === "string") { @@ -257,19 +340,10 @@ export function evaluateLogic({ if (child !== undefined) { return child.stateValues.value.tree; } - child = dependencyValues.mathListChildrenByCode[tree]; - if (child !== undefined) { - return ["list", ...child.stateValues.maths.map((x) => x.tree)]; - } child = dependencyValues.numberChildrenByCode[tree]; if (child !== undefined) { return numberToMathExpression(child.stateValues.value).tree; } - child = dependencyValues.numberListChildrenByCode[tree]; - - if (child !== undefined) { - return ["list", ...child.stateValues.numbers]; - } return tree; } if (!Array.isArray(tree)) { @@ -355,43 +429,47 @@ export function evaluateLogic({ return valueOnInvalid; } - let foundInvalidFormat = false; - let foundUnorderedList = false; - // every operand must be a boolean, booleanlist, or a string that is true or false - operands = operands.map(function (x) { - if (typeof x === "string") { - let child = dependencyValues.booleanChildrenByCode[x]; - if (child !== undefined) { - return child.stateValues.value; - } - child = dependencyValues.booleanListChildrenByCode[x]; + let foundUnordered = false; + let replaceBooleanAndFindUnordered = function (tree) { + if (typeof tree === "string") { + let child = dependencyValues.booleanChildrenByCode[tree]; if (child !== undefined) { - if (child.stateValues.unordered) { - foundUnorderedList = true; + if (child.stateValues.inUnorderedList) { + foundUnordered = true; } - return child.stateValues.booleans; + return child.stateValues.value; } - x = x.toLowerCase().trim(); - if (x === "true" || x === "t") { + tree = tree.toLowerCase().trim(); + if (tree === "true") { return true; } - if (x === "false" || x === "f") { + if (tree === "false") { return false; } - foundInvalidFormat = true; - return valueOnInvalid; + + throw Error("Invalid format"); } - foundInvalidFormat = true; - return valueOnInvalid; - }); - if (foundInvalidFormat) { + if (!Array.isArray(tree)) { + throw Error("Invalid format"); + } + + if (tree[0] === "list") { + return tree.slice(1).map(replaceBooleanAndFindUnordered); + } else { + return evaluateSub(tree) === 1 ? true : false; + } + }; + + try { + operands = operands.map(replaceBooleanAndFindUnordered); + } catch (e) { return valueOnInvalid; } let unorderedCompare = dependencyValues.unorderedCompare; if (canOverrideUnorderedCompare) { - if (foundUnorderedList) { + if (foundUnordered) { unorderedCompare = true; } } @@ -471,6 +549,15 @@ export function evaluateLogic({ let booleanList1 = operands[0]; let booleanList2 = operands[1]; + // since single lists are indistinguishable from singletons, + // turn single booleans into arrays + if (typeof booleanList1 === "boolean") { + booleanList1 = [booleanList1]; + } + if (typeof booleanList2 === "boolean") { + booleanList2 = [booleanList2]; + } + if ( !( operands.length === 2 && @@ -508,22 +595,16 @@ export function evaluateLogic({ return valueOnInvalid; } - let foundUnorderedList = false; + let foundUnordered = false; let replaceTextAndFindUnordered = function (tree, recurse = true) { if (typeof tree === "string") { let child = dependencyValues.textChildrenByCode[tree]; if (child !== undefined) { - return child.stateValues.value.trim().replace(/\s+/, " "); - } - child = dependencyValues.textListChildrenByCode[tree]; - if (child !== undefined) { - if (child.stateValues.unordered) { - foundUnorderedList = true; + if (child.stateValues.inUnorderedList) { + foundUnordered = true; } - return child.stateValues.texts.map((x) => - x.trim().replace(/\s+/, " "), - ); + return child.stateValues.value.trim().replace(/\s+/, " "); } return tree.trim(); } @@ -532,27 +613,34 @@ export function evaluateLogic({ return tree.toString(); } - // multiple words would become multiplication - if (!(recurse && Array.isArray(tree) && tree[0] === "*")) { - throw Error("Invalid format"); + if (recurse && Array.isArray(tree)) { + if (tree[0] === "*") { + // multiple words would become multiplication + return tree + .slice(1) + .map((x) => replaceTextAndFindUnordered(x, false)) + .join(" "); + } else if (tree[0] === "list") { + return tree + .slice(1) + .map((x) => replaceTextAndFindUnordered(x, false)) + .map((x) => x.trim().replace(/\s+/, " ")); + } } - return tree - .slice(1) - .map((x) => replaceTextAndFindUnordered(x, false)) - .join(" "); + throw Error("Invalid format"); }; try { // every operand must be a text or string - operands = operands.map(replaceTextAndFindUnordered); + operands = operands.map((x) => replaceTextAndFindUnordered(x)); } catch (e) { return valueOnInvalid; } let unorderedCompare = dependencyValues.unorderedCompare; if (canOverrideUnorderedCompare) { - if (foundUnorderedList) { + if (foundUnordered) { unorderedCompare = true; } } @@ -661,6 +749,15 @@ export function evaluateLogic({ let textList1 = operands[0]; let textList2 = operands[1]; + // since single lists are indistinguishable from singletons, + // turn single texts into arrays + if (typeof textList1 === "string") { + textList1 = [textList1]; + } + if (typeof textList2 === "string") { + textList2 = [textList2]; + } + if ( !( operands.length === 2 && @@ -742,7 +839,7 @@ export function evaluateLogic({ return 0; } - // no boolean or text, just math, mathList, number, numberList, and strings + // no boolean or text, just math, number, and strings let strict; if (operator === "lts" || operator === "gts") { @@ -761,23 +858,12 @@ export function evaluateLogic({ } return child.stateValues.value.tree; } - child = dependencyValues.mathListChildrenByCode[tree]; - if (child !== undefined) { - if (child.stateValues.unordered) { - foundUnordered = true; - } - return ["list", ...child.stateValues.maths.map((x) => x.tree)]; - } child = dependencyValues.numberChildrenByCode[tree]; if (child !== undefined) { - return numberToMathExpression(child.stateValues.value).tree; - } - child = dependencyValues.numberListChildrenByCode[tree]; - if (child !== undefined) { - if (child.stateValues.unordered) { + if (child.stateValues.inUnorderedList) { foundUnordered = true; } - return ["list", ...child.stateValues.numbers]; + return numberToMathExpression(child.stateValues.value).tree; } return tree; } @@ -898,10 +984,79 @@ export function evaluateLogic({ let element = mathOperands[0]; let set = mathOperands[1]; + + // operator is in or notin + // If first operand is a number and second operand can be turned into a subset of reals, + // then we can check for inclusion. + + let number1 = element.evaluate_to_constant(); + + // Note: since single lists are indistinguishable from singletons, + // we accept the behavior that buildSubsetFromMathExpression will turn + // a number into a singleton set + + if (Number.isFinite(number1)) { + let subsetOfReals = buildSubsetFromMathExpression(set); + + if (subsetOfReals.isValid()) { + let containsNumber = subsetOfReals.containsElement(number1); + if (operator === "in") { + return containsNumber ? 1 : 0; + } else { + // notin + return containsNumber ? 0 : 1; + } + } + } + let set_tree = set.tree; - if (Array.isArray(set_tree) && ["set", "list"].includes(set_tree[0])) { - if (dependencyValues.matchPartial) { - let results = set_tree.slice(1).map((x) => + + // since single lists are indistinguishable from singletons, + // turn single values into lists + if ( + !(Array.isArray(set_tree) && ["set", "list"].includes(set_tree[0])) + ) { + set_tree = ["list", set_tree]; + } + + if (dependencyValues.matchPartial) { + let results = set_tree.slice(1).map((x) => + checkEquality({ + object1: element, + object2: me.fromAst(x), + isUnordered: unorderedCompare, + partialMatches: dependencyValues.matchPartial, + matchByExactPositions: + dependencyValues.matchByExactPositions, + symbolicEquality: dependencyValues.symbolicEquality, + simplify: dependencyValues.simplifyOnCompare, + expand: dependencyValues.expandOnCompare, + allowedErrorInNumbers: + dependencyValues.allowedErrorInNumbers, + includeErrorInNumberExponents: + dependencyValues.includeErrorInNumberExponents, + allowedErrorIsAbsolute: + dependencyValues.allowedErrorIsAbsolute, + numSignErrorsMatched: dependencyValues.numSignErrorsMatched, + numPeriodicSetMatchesRequired: + dependencyValues.numPeriodicSetMatchesRequired, + caseInsensitiveMatch: dependencyValues.caseInsensitiveMatch, + matchBlanks: dependencyValues.matchBlanks, + }), + ); + + let max_fraction = results.reduce( + (a, c) => Math.max(a, c.fraction_equal), + 0, + ); + if (operator === "in") { + return max_fraction; + } else { + return 1 - max_fraction; + } + } else { + let result = set_tree.slice(1).some( + (x) => checkEquality({ object1: element, object2: me.fromAst(x), @@ -925,80 +1080,15 @@ export function evaluateLogic({ caseInsensitiveMatch: dependencyValues.caseInsensitiveMatch, matchBlanks: dependencyValues.matchBlanks, - }), - ); + }).fraction_equal === 1, + ); - let max_fraction = results.reduce( - (a, c) => Math.max(a, c.fraction_equal), - 0, - ); - if (operator === "in") { - return max_fraction; - } else { - return 1 - max_fraction; - } + if (operator === "in") { + return result ? 1 : 0; } else { - let result = set_tree.slice(1).some( - (x) => - checkEquality({ - object1: element, - object2: me.fromAst(x), - isUnordered: unorderedCompare, - partialMatches: dependencyValues.matchPartial, - matchByExactPositions: - dependencyValues.matchByExactPositions, - symbolicEquality: dependencyValues.symbolicEquality, - simplify: dependencyValues.simplifyOnCompare, - expand: dependencyValues.expandOnCompare, - allowedErrorInNumbers: - dependencyValues.allowedErrorInNumbers, - includeErrorInNumberExponents: - dependencyValues.includeErrorInNumberExponents, - allowedErrorIsAbsolute: - dependencyValues.allowedErrorIsAbsolute, - numSignErrorsMatched: - dependencyValues.numSignErrorsMatched, - numPeriodicSetMatchesRequired: - dependencyValues.numPeriodicSetMatchesRequired, - caseInsensitiveMatch: - dependencyValues.caseInsensitiveMatch, - matchBlanks: dependencyValues.matchBlanks, - }).fraction_equal === 1, - ); - - if (operator === "in") { - return result ? 1 : 0; - } else { - return result ? 0 : 1; - } + return result ? 0 : 1; } } - - // operator is in or notin, but second operand is not a set or list - // If first operand is a number and second operand can be turned into a subset of reals, - // then we can check for inclusion. - - let number1 = element.evaluate_to_constant(); - let number2 = set.evaluate_to_constant(); - - // Note: since buildSubsetFromMathExpression will create a subset from a number, - // we exclude this case to make it consistent with the fact that non-numerical - // single values are not treated as sets. - if (Number.isFinite(number1) && !Number.isFinite(number2)) { - let subsetOfReals = buildSubsetFromMathExpression(set); - - if (subsetOfReals.isValid()) { - let containsNumber = subsetOfReals.containsElement(number1); - if (operator === "in") { - return containsNumber ? 1 : 0; - } else { - // notin - return containsNumber ? 0 : 1; - } - } - } - - return valueOnInvalid; } if (["subset", "notsubset", "superset", "notsuperset"].includes(operator)) { @@ -1014,84 +1104,91 @@ export function evaluateLogic({ [set1, set2] = [set2, set1]; } - let set1_tree = set1.tree; - let set2_tree = set2.tree; - - if ( - Array.isArray(set1_tree) && - ["set", "list"].includes(set1_tree[0]) && - Array.isArray(set2_tree) && - ["set", "list"].includes(set2_tree[0]) - ) { - // check if every element in set 1 is equal to an element in set 2 - let haveContainment = set1_tree.slice(1).every((elt1) => - set2_tree.slice(1).some( - (elt2) => - checkEquality({ - object1: me.fromAst(elt1), - object2: me.fromAst(elt2), - isUnordered: unorderedCompare, - partialMatches: dependencyValues.matchPartial, - matchByExactPositions: - dependencyValues.matchByExactPositions, - symbolicEquality: dependencyValues.symbolicEquality, - simplify: dependencyValues.simplifyOnCompare, - expand: dependencyValues.expandOnCompare, - allowedErrorInNumbers: - dependencyValues.allowedErrorInNumbers, - includeErrorInNumberExponents: - dependencyValues.includeErrorInNumberExponents, - allowedErrorIsAbsolute: - dependencyValues.allowedErrorIsAbsolute, - numSignErrorsMatched: - dependencyValues.numSignErrorsMatched, - numPeriodicSetMatchesRequired: - dependencyValues.numPeriodicSetMatchesRequired, - caseInsensitiveMatch: - dependencyValues.caseInsensitiveMatch, - matchBlanks: dependencyValues.matchBlanks, - }).fraction_equal === 1, - ), - ); - - if (operator.substring(0, 3) === "not") { - return haveContainment ? 0 : 1; - } else { - return haveContainment ? 1 : 0; - } - } - // operator is subset, notsubset, superset, or notsuperset, // but operands are not lists or sets // If both operands can be turned into a subset of reals, // then we can check for inclusion. - // Note: since buildSubsetFromMathExpression will create a subset from a number, - // we exclude this case to make it consistent with the fact that non-numerical - // single values are not treated as sets. - let number1 = set1.evaluate_to_constant(); - let number2 = set2.evaluate_to_constant(); + // Note: since single lists are indistinguishable from singletons, + // we accept the behavior that buildSubsetFromMathExpression will turn + // a number into a singleton set - if (!(Number.isFinite(number1) || Number.isFinite(number2))) { - let subsetOfReals1 = buildSubsetFromMathExpression(set1); + let subsetOfReals1 = buildSubsetFromMathExpression(set1); - if (subsetOfReals1.isValid()) { - let subsetOfReals2 = buildSubsetFromMathExpression(set2); + if (subsetOfReals1.isValid()) { + let subsetOfReals2 = buildSubsetFromMathExpression(set2); - if (subsetOfReals2.isValid()) { - let haveContainment = - subsetOfReals2.containsSubset(subsetOfReals1); + if (subsetOfReals2.isValid()) { + let haveContainment = + subsetOfReals2.containsSubset(subsetOfReals1); - if (operator.substring(0, 3) === "not") { - return haveContainment ? 0 : 1; - } else { - return haveContainment ? 1 : 0; - } + if (operator.substring(0, 3) === "not") { + return haveContainment ? 0 : 1; + } else { + return haveContainment ? 1 : 0; } } } - return valueOnInvalid; + let set1_tree = set1.tree; + let set2_tree = set2.tree; + + // since single lists are indistinguishable from singletons, + // turn single values into lists + + if ( + !( + Array.isArray(set1_tree) && + ["set", "list"].includes(set1_tree[0]) + ) + ) { + set1_tree = ["list", set1_tree]; + } + if ( + !( + Array.isArray(set2_tree) && + ["set", "list"].includes(set2_tree[0]) + ) + ) { + set2_tree = ["list", set2_tree]; + } + + // check if every element in set 1 is equal to an element in set 2 + let haveContainment = set1_tree.slice(1).every((elt1) => + set2_tree.slice(1).some( + (elt2) => + checkEquality({ + object1: me.fromAst(elt1), + object2: me.fromAst(elt2), + isUnordered: unorderedCompare, + partialMatches: dependencyValues.matchPartial, + matchByExactPositions: + dependencyValues.matchByExactPositions, + symbolicEquality: dependencyValues.symbolicEquality, + simplify: dependencyValues.simplifyOnCompare, + expand: dependencyValues.expandOnCompare, + allowedErrorInNumbers: + dependencyValues.allowedErrorInNumbers, + includeErrorInNumberExponents: + dependencyValues.includeErrorInNumberExponents, + allowedErrorIsAbsolute: + dependencyValues.allowedErrorIsAbsolute, + numSignErrorsMatched: + dependencyValues.numSignErrorsMatched, + numPeriodicSetMatchesRequired: + dependencyValues.numPeriodicSetMatchesRequired, + caseInsensitiveMatch: + dependencyValues.caseInsensitiveMatch, + matchBlanks: dependencyValues.matchBlanks, + }).fraction_equal === 1, + ), + ); + + if (operator.substring(0, 3) === "not") { + return haveContainment ? 0 : 1; + } else { + return haveContainment ? 1 : 0; + } } // since have inequality, all operands must be numbers @@ -1173,8 +1270,20 @@ export function splitSymbolsIfMath({ ]; } - if (operands.some((x) => nonMathCodes.includes(x))) { - foundNonMath = true; + // check if an operand is a non-math or a list containing a non-math + for (let op of operands) { + if (nonMathCodes.includes(op)) { + foundNonMath = true; + break; + } + if ( + Array.isArray(op) && + op[0] === "list" && + op.slice(1).some((x) => nonMathCodes.includes(x)) + ) { + foundNonMath = true; + break; + } } if (operator === "apply") { diff --git a/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts b/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts new file mode 100644 index 000000000..5abb425c7 --- /dev/null +++ b/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts @@ -0,0 +1,676 @@ +import { convertValueToMathExpression, vectorOperators } from "@doenet/utils"; +import { returnRoundingAttributeComponentShadowing } from "./rounding"; +//@ts-ignore +import me from "math-expressions"; + +const vectorAndListOperators = ["list", ...vectorOperators]; + +export function returnMathVectorMatrixStateVariableDefinitions() { + let stateVariableDefinitions: any = {}; + + stateVariableDefinitions.numDimensions = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }), + definition({ dependencyValues }: { dependencyValues: { value: any } }) { + let numDimensions = 1; + + let tree = dependencyValues.value.tree; + + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + numDimensions = tree.length - 1; + } else if (tree[0] === "matrix") { + let size = tree[1].slice(1); + + if (size[0] === 1) { + numDimensions = size[1]; + } else if (size[1] === 1) { + numDimensions = size[0]; + } + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + numDimensions = tree[1].length - 1; + } + } + + return { setValue: { numDimensions } }; + }, + }; + + stateVariableDefinitions.vector = { + public: true, + shadowingInstructions: { + createComponentOfType: "math", + addAttributeComponentsShadowingStateVariables: + returnRoundingAttributeComponentShadowing(), + returnWrappingComponents(prefix: string | undefined) { + if (prefix === "x") { + return []; + } else { + // entire array + // wrap by both and + return [ + [ + "vector", + { + componentType: "mathList", + isAttributeNamed: "xs", + }, + ], + ]; + } + }, + }, + isArray: true, + entryPrefixes: ["x"], + returnArraySizeDependencies: () => ({ + numDimensions: { + dependencyType: "stateVariable", + variableName: "numDimensions", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { numDimensions: number }; + }) { + return [dependencyValues.numDimensions]; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }; + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + arraySize, + }: { + globalDependencyValues: { value: any }; + arraySize: number[]; + }) { + let tree = globalDependencyValues.value.tree; + + let createdVector = false; + + let vector: Record = {}; + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + for (let ind = 0; ind < arraySize[0]; ind++) { + vector[ind] = me.fromAst(tree[ind + 1]); + } + createdVector = true; + } else if (tree[0] === "matrix") { + let size = tree[1].slice(1); + if (size[0] === 1) { + for (let ind = 0; ind < arraySize[0]; ind++) { + vector[ind] = me.fromAst(tree[2][1][ind + 1]); + } + createdVector = true; + } else if (size[1] === 1) { + for (let ind = 0; ind < arraySize[0]; ind++) { + vector[ind] = me.fromAst(tree[2][ind + 1][1]); + } + createdVector = true; + } + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + for (let ind = 0; ind < arraySize[0]; ind++) { + vector[ind] = me.fromAst(tree[1][ind + 1]); + } + createdVector = true; + } + } + if (!createdVector) { + vector[0] = globalDependencyValues.value; + } + + return { setValue: { vector } }; + }, + async inverseArrayDefinitionByKey({ + desiredStateVariableValues, + globalDependencyValues, + stateValues, + workspace, + arraySize, + }: { + desiredStateVariableValues: { vector: any[] }; + globalDependencyValues: { value: any }; + stateValues: { vector: Promise }; + workspace: any; + arraySize: number[]; + }) { + // in case just one ind specified, merge with previous values + if (!workspace.desiredVector) { + workspace.desiredVector = []; + } + for (let ind = 0; ind < arraySize[0]; ind++) { + if (desiredStateVariableValues.vector[ind] !== undefined) { + workspace.desiredVector[ind] = convertValueToMathExpression( + desiredStateVariableValues.vector[ind], + ); + } else if (workspace.desiredVector[ind] === undefined) { + workspace.desiredVector[ind] = (await stateValues.vector)[ + ind + ]; + } + } + + let desiredValue; + let tree = globalDependencyValues.value.tree; + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + desiredValue = me.fromAst([ + tree[0], + ...workspace.desiredVector.map((x: any) => x.tree), + ]); + } else if (tree[0] === "matrix") { + let size = tree[1].slice(1); + if (size[0] === 1) { + let desiredMatrixVals: any[] = ["tuple"]; + for (let ind = 0; ind < arraySize[0]; ind++) { + desiredMatrixVals.push( + workspace.desiredVector[ind], + ); + } + desiredMatrixVals = ["tuple", desiredMatrixVals]; + desiredValue = me.fromAst([ + "matrix", + tree[1], + desiredMatrixVals, + ]); + } else if (size[1] === 1) { + let desiredMatrixVals: any[] = ["tuple"]; + for (let ind = 0; ind < arraySize[0]; ind++) { + desiredMatrixVals.push([ + "tuple", + workspace.desiredVector[ind], + ]); + } + desiredValue = me.fromAst([ + "matrix", + tree[1], + desiredMatrixVals, + ]); + } + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + desiredValue = [ + tree[0], + [ + tree[1][0], + ...workspace.desiredVector.map((x: any) => x.tree), + ], + ]; + if (tree[2]) { + desiredValue.push(tree[2]); + } + desiredValue = me.fromAst(desiredValue); + } + } + + if (!desiredValue) { + desiredValue = workspace.desiredVector[0]; + } + + let instructions = [ + { + setDependency: "value", + desiredValue, + }, + ]; + + return { + success: true, + instructions, + }; + }, + }; + + stateVariableDefinitions.x = { + isAlias: true, + targetVariableName: "x1", + }; + + stateVariableDefinitions.y = { + isAlias: true, + targetVariableName: "x2", + }; + + stateVariableDefinitions.z = { + isAlias: true, + targetVariableName: "x3", + }; + + stateVariableDefinitions.matrixSize = { + public: true, + shadowingInstructions: { + createComponentOfType: "numberList", + }, + returnDependencies: () => ({ + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }), + definition({ dependencyValues }: { dependencyValues: { value: any } }) { + let matrixSize = [1, 1]; + + let tree = dependencyValues.value.tree; + + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + matrixSize = [tree.length - 1, 1]; + } else if (tree[0] === "matrix") { + matrixSize = tree[1].slice(1); + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + matrixSize = [1, tree[1].length - 1]; + } + } + + return { setValue: { matrixSize } }; + }, + }; + + stateVariableDefinitions.numRows = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + matrixSize: { + dependencyType: "stateVariable", + variableName: "matrixSize", + }, + }), + definition({ + dependencyValues, + }: { + dependencyValues: { matrixSize: number[] }; + }) { + return { + setValue: { numRows: dependencyValues.matrixSize[0] }, + }; + }, + }; + + stateVariableDefinitions.numColumns = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + matrixSize: { + dependencyType: "stateVariable", + variableName: "matrixSize", + }, + }), + definition({ + dependencyValues, + }: { + dependencyValues: { matrixSize: number[] }; + }) { + return { + setValue: { numColumns: dependencyValues.matrixSize[1] }, + }; + }, + }; + + stateVariableDefinitions.matrix = { + public: true, + shadowingInstructions: { + createComponentOfType: "math", + addAttributeComponentsShadowingStateVariables: + returnRoundingAttributeComponentShadowing(), + returnWrappingComponents(prefix: string | undefined) { + if (prefix === "matrixEntry") { + return []; + } else if (prefix === "row") { + return [["matrix", "matrixRow"]]; + } else if (prefix === "column") { + return [["matrix", "matrixColumn"]]; + } else { + // entire matrix + // wrap inner dimension by matrixRow and outer dimension by matrix + return [["matrixRow"], ["matrix"]]; + } + }, + }, + isArray: true, + numDimensions: 2, + entryPrefixes: ["matrixEntry", "row", "column", "rows", "columns"], + returnEntryDimensions: (prefix: string | undefined) => { + if (prefix === "matrixEntry") { + return 0; + } else if (prefix === "rows" || prefix === "columns") { + return 2; + } else { + return 1; + } + }, + getArrayKeysFromVarName({ + arrayEntryPrefix, + varEnding, + arraySize, + }: { + arrayEntryPrefix: string; + varEnding: string; + arraySize: number[]; + }) { + if (arrayEntryPrefix === "matrixEntry") { + // matrixEntry1_2 is the 2nd entry from the first row + let indices = varEnding.split("_").map((x) => Number(x) - 1); + if ( + indices.length === 2 && + indices.every((x) => Number.isInteger(x) && x >= 0) + ) { + if (arraySize) { + if (indices.every((x, i) => x < arraySize[i])) { + return [String(indices)]; + } else { + return []; + } + } else { + // If not given the array size, + // then return the array keys assuming the array is large enough. + // Must do this as it is used to determine potential array entries. + return [String(indices)]; + } + } else { + return []; + } + } else if (arrayEntryPrefix === "row") { + // row3 is all components of the third row + + let rowInd = Number(varEnding) - 1; + if (!(Number.isInteger(rowInd) && rowInd >= 0)) { + return []; + } + + if (!arraySize) { + // If don't have array size, we just need to determine if it is a potential entry. + // Return the first entry assuming array is large enough + return [rowInd + ",0"]; + } + if (rowInd < arraySize[0]) { + // array of "rowInd,i", where i=0, ..., arraySize[1]-1 + return Array.from( + Array(arraySize[1]), + (_, i) => rowInd + "," + i, + ); + } else { + return []; + } + } else if (arrayEntryPrefix === "column") { + // column3 is all components of the third column + + let colInd = Number(varEnding) - 1; + if (!(Number.isInteger(colInd) && colInd >= 0)) { + return []; + } + + if (!arraySize) { + // If don't have array size, we just need to determine if it is a potential entry. + // Return the first entry assuming array is large enough + return ["0," + colInd]; + } + if (colInd < arraySize[1]) { + // array of "i,colInd", where i=0, ..., arraySize[1]-1 + return Array.from( + Array(arraySize[0]), + (_, i) => i + "," + colInd, + ); + } else { + return []; + } + } else if ( + arrayEntryPrefix === "rows" || + arrayEntryPrefix === "columns" + ) { + // rows or columns is the whole matrix + // (this are designed for getting rows and columns using propIndex) + // (rows and matrix are the same, but rows is added to be parallel to columns) + + if (!arraySize) { + // If don't have array size, we justr eturn the first entry + return ["0,0"]; + } + let keys = []; + for (let rowInd = 0; rowInd < arraySize[0]; rowInd++) { + keys.push( + ...Array.from( + Array(arraySize[1]), + (_, i) => rowInd + "," + i, + ), + ); + } + return keys; + } + }, + arrayVarNameFromPropIndex(propIndex: number[], varName: string) { + if (varName === "matrix" || varName === "rows") { + if (propIndex.length === 1) { + return "row" + propIndex[0]; + } else { + // if propIndex has additional entries, ignore them + return `matrixEntry${propIndex[0]}_${propIndex[1]}`; + } + } + if (varName === "columns") { + if (propIndex.length === 1) { + return "column" + propIndex[0]; + } else { + // if propIndex has additional entries, ignore them + return `matrixEntry${propIndex[1]}_${propIndex[0]}`; + } + } + if (varName.slice(0, 3) === "row") { + let rowNum = Number(varName.slice(3)); + if (Number.isInteger(rowNum) && rowNum > 0) { + // if propIndex has additional entries, ignore them + return `matrixEntry${rowNum}_${propIndex[0]}`; + } + } + if (varName.slice(0, 6) === "column") { + let colNum = Number(varName.slice(6)); + if (Number.isInteger(colNum) && colNum > 0) { + // if propIndex has additional entries, ignore them + return `matrixEntry${propIndex[0]}_${colNum}`; + } + } + return null; + }, + returnArraySizeDependencies: () => ({ + matrixSize: { + dependencyType: "stateVariable", + variableName: "matrixSize", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { matrixSize: number[] }; + }) { + return dependencyValues.matrixSize; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }; + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + arraySize, + }: { + globalDependencyValues: { value: any }; + arraySize: number[]; + }) { + let tree = globalDependencyValues.value.tree; + + let createdMatrix = false; + + let matrix: Record = {}; + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + for (let ind = 0; ind < arraySize[0]; ind++) { + matrix[ind + ",0"] = me.fromAst(tree[ind + 1]); + } + createdMatrix = true; + } else if (tree[0] === "matrix") { + let matVals = tree[2]; + for (let i = 0; i < arraySize[0]; i++) { + for (let j = 0; j < arraySize[1]; j++) { + matrix[`${i},${j}`] = me.fromAst( + matVals[i + 1][j + 1], + ); + } + } + createdMatrix = true; + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + for (let ind = 0; ind < arraySize[1]; ind++) { + matrix["0," + ind] = me.fromAst(tree[1][ind + 1]); + } + createdMatrix = true; + } + } + if (!createdMatrix) { + matrix["0,0"] = globalDependencyValues.value; + } + + return { setValue: { matrix } }; + }, + async inverseArrayDefinitionByKey({ + desiredStateVariableValues, + globalDependencyValues, + stateValues, + workspace, + arraySize, + }: { + desiredStateVariableValues: { matrix: Record }; + globalDependencyValues: { value: any }; + stateValues: { matrix: Promise }; + workspace: any; + arraySize: number[]; + }) { + // in case just one ind specified, merge with previous values + if (!workspace.desiredMatrix) { + workspace.desiredMatrix = []; + } + for (let i = 0; i < arraySize[0]; i++) { + for (let j = 0; j < arraySize[1]; j++) { + let arrayKey = i + "," + j; + if ( + desiredStateVariableValues.matrix[arrayKey] !== + undefined + ) { + workspace.desiredMatrix[arrayKey] = + convertValueToMathExpression( + desiredStateVariableValues.matrix[arrayKey], + ); + } else if ( + workspace.desiredMatrix[arrayKey] === undefined + ) { + workspace.desiredMatrix[arrayKey] = ( + await stateValues.matrix + )[i][j]; + } + } + } + + let desiredValue; + let tree = globalDependencyValues.value.tree; + if (Array.isArray(tree)) { + if (vectorAndListOperators.includes(tree[0])) { + desiredValue = [tree[0]]; + for (let ind = 0; ind < arraySize[0]; ind++) { + desiredValue.push( + workspace.desiredMatrix[ind + ",0"].tree, + ); + } + } else if (tree[0] === "matrix") { + let desiredMatrixVals: any[] = ["tuple"]; + + for (let i = 0; i < arraySize[0]; i++) { + let row = ["tuple"]; + for (let j = 0; j < arraySize[1]; j++) { + row.push(workspace.desiredMatrix[`${i},${j}`].tree); + } + desiredMatrixVals.push(row); + } + desiredValue = me.fromAst([ + "matrix", + tree[1], + desiredMatrixVals, + ]); + } else if ( + vectorOperators.includes(tree[1][0]) && + ((tree[0] === "^" && tree[2] === "T") || + tree[0] === "prime") + ) { + desiredValue = [tree[0]]; + let desiredVector = [tree[1][0]]; + for (let ind = 0; ind < arraySize[1]; ind++) { + desiredVector.push( + workspace.desiredMatrix["0," + ind].tree, + ); + } + desiredValue = [tree[0], desiredVector]; + if (tree[2]) { + desiredValue.push(tree[2]); + } + desiredValue = me.fromAst(desiredValue); + } + } + + if (!desiredValue) { + desiredValue = workspace.desiredMatrix["0,0"]; + } + + let instructions = [ + { + setDependency: "value", + desiredValue, + }, + ]; + + return { + success: true, + instructions, + }; + }, + }; + + return stateVariableDefinitions; +} diff --git a/packages/doenetml-worker/src/utils/parseMath.ts b/packages/doenetml-worker/src/utils/parseMath.ts new file mode 100644 index 000000000..9e14d9a4a --- /dev/null +++ b/packages/doenetml-worker/src/utils/parseMath.ts @@ -0,0 +1,375 @@ +type CompositeReplacementRange = { + firstInd: number; + lastInd: number; + asList: boolean; + hidden: boolean; + potentialListComponents: boolean[]; +}; + +// concatenate strings with a numbered code for each non-string child +// (that will be parsed to form express}ion with codes) +// Add commas between the components that are all from one composite, +// if that composite has asList set to true. +// Put parens around that list in some cases, as described below. +export function createInputStringFromChildren({ + children, + codePre, + format, + createInternalLists = false, + parser, +}: { + children: any; + codePre: string; + format: "latex" | "text"; + createInternalLists?: boolean; + parser: (arg0: string) => any; +}) { + let nonStringInd = 0; + let nonStringIndByChild: (null | number)[] = []; + for (let child of children) { + if (typeof child === "string") { + nonStringIndByChild.push(null); + } else { + nonStringIndByChild.push(nonStringInd); + nonStringInd++; + } + } + + let result = createInputStringFromChildrenSub({ + compositeReplacementRange: children.compositeReplacementRange, + children, + startInd: 0, + endInd: children.length - 1, + nonStringIndByChild, + format, + codePre, + createInternalLists, + nextInternalListInd: nonStringInd, + parser, + }); + + return { + string: result.newChildren.join(""), + internalLists: result.internalLists, + }; +} + +function createInputStringFromChildrenSub({ + compositeReplacementRange, + children, + startInd, + endInd, + nonStringIndByChild, + format, + codePre, + potentialListComponents, + createInternalLists, + nextInternalListInd, + parser, +}: { + compositeReplacementRange: CompositeReplacementRange[] | undefined; + children: any; + startInd: number; + endInd: number; + nonStringIndByChild: (null | number)[]; + format: "latex" | "text"; + codePre: string; + potentialListComponents?: boolean[]; + createInternalLists: boolean; + nextInternalListInd: number; + parser: (arg0: string) => any; +}): { + newChildren: string[]; + newPotentialListComponents: boolean[]; + internalLists: Record; +} { + let newChildren: string[] = []; + let newPotentialListComponents: boolean[] = []; + let lastChildInd = startInd - 1; + let internalLists: Record = {}; + + let leftDelimiters = ["{", "[", "(", "|", ","]; + let rightDelimiters = ["}", "]", ")", "|", ","]; + + if (compositeReplacementRange) { + for ( + let rangeInd = 0; + rangeInd < compositeReplacementRange.length; + rangeInd++ + ) { + let range = compositeReplacementRange[rangeInd]; + + let rangeFirstInd = range.firstInd; + let rangeLastInd = range.lastInd; + + if (rangeFirstInd > lastChildInd && rangeLastInd <= endInd) { + if (lastChildInd + 1 < rangeFirstInd) { + for ( + let ind = lastChildInd + 1; + ind < rangeFirstInd; + ind++ + ) { + // we are not grouping these children + // but we are separately turning each one into a string + // (turning the non-string children into a code based on codePre and its nonStringInd) + newChildren.push( + baseStringFromChildren({ + children, + startInd: ind, + endInd: ind, + nonStringIndByChild, + format, + codePre, + }), + ); + } + if (potentialListComponents) { + // Since we didn't change the components, + // their status of being a potential list component is not changed + newPotentialListComponents.push( + ...potentialListComponents.slice( + lastChildInd - startInd + 1, + rangeFirstInd - startInd, + ), + ); + } + } + + // If a composite produced composites that produced children, + // then this outer composite is first in the array of replacement ranges. + // We first process the children corresponding to any of these replacement composites, + // which will concatenate the replacements of each composite into a single text, + // which may be be turned into a list according to the settings of that composite. + + // We remove the replacement range of the current composite (any all earlier ones) + let subReplacementRange = compositeReplacementRange.slice( + rangeInd + 1, + ); + + let { + newChildren: childrenInRange, + newPotentialListComponents: potentialListComponentsInRange, + internalLists: newInternalLists, + } = createInputStringFromChildrenSub({ + compositeReplacementRange: subReplacementRange, + children, + startInd: rangeFirstInd, + endInd: rangeLastInd, + nonStringIndByChild, + format, + codePre, + potentialListComponents: range.potentialListComponents, + createInternalLists, + nextInternalListInd, + parser, + }); + + Object.assign(internalLists, newInternalLists); + nextInternalListInd += Object.keys(newInternalLists).length; + + let allAreListComponents = potentialListComponentsInRange.every( + (x) => x, + ); + + if ( + range.asList && + allAreListComponents && + childrenInRange.length > 1 + ) { + // add commas between all children from a single composite + let listString = childrenInRange + .filter((v) => v.trim() !== "") + .map((v, i, a) => + i === a.length - 1 ? v : v.trimEnd(), + ) + .join(", "); + + // The following implements the logic to determine if this comma-separated list + // should be wrapped by parens. + // Wrap with parens if the lists is surrounded by a non-delimiter on either side. + // The parens will generally turn the list into a tuple (or to arguments of a function) + // when it is parsed into a math-expression. + + let wrap = false; + + // First check if there is a non-delimiter to the left + if (rangeFirstInd > 0) { + let prevInd = rangeFirstInd - 1; + while (prevInd >= 0) { + let prevChild = children[prevInd]; + if (typeof prevChild === "string") { + prevChild = prevChild.trim(); + if (prevChild.length > 0) { + if ( + !leftDelimiters.includes( + prevChild[prevChild.length - 1], + ) + ) { + // The string to the left did not contain one of the delimiters, + // so we must wrap the list. + wrap = true; + } + break; + } + } else { + // There is a non-string child to the left, + // so we must wrap the list + wrap = true; + } + } + } + + if (!wrap) { + // Since we didn't have a non-delimiter to the left, + // check if there is a non-delimiter to the right. + if (rangeLastInd < children.length - 1) { + let nextInd = rangeLastInd + 1; + while (nextInd <= children.length - 1) { + let nextChild = children[nextInd]; + if (typeof nextChild === "string") { + nextChild = nextChild.trim(); + if (nextChild.length > 0) { + let nextChar = nextChild[0]; + // If the format is latex, + // the delimiter could be escaped by a \ + if ( + format === "latex" && + nextChar === "\\" && + nextChild.length > 1 + ) { + nextChar = nextChild[1]; + } + if ( + !rightDelimiters.includes(nextChar) + ) { + // The string to the right did not contain one of the delimiters, + // so we must wrap the list. + wrap = true; + } + break; + } + } else { + // There is a non-string child to the right, + // so we must wrap the list + wrap = true; + } + } + } + } + + if (wrap) { + if (createInternalLists) { + // if `createInternalLists` is set, rather than wrapping in parens, + // we will put a list into the ast at this point + // (even though one wouldn't be able to get that by parsing a string into math) + let code = codePre + nextInternalListInd; + internalLists[code] = parser(listString); + nextInternalListInd++; + listString = returnStringForCode(format, code); + } else { + listString = "(" + listString + ")"; + } + } + + newChildren.push(listString); + } else { + // We are not turning the children in a list, + // so just concatenate the strings + newChildren.push(childrenInRange.join("")); + } + + if (potentialListComponents) { + // record whether the result from the composite (a single string now) + // should be considered a list component for any outer composite + newPotentialListComponents.push(allAreListComponents); + } + lastChildInd = rangeLastInd; + } + } + } + + if (lastChildInd < endInd) { + for (let ind = lastChildInd + 1; ind <= endInd; ind++) { + // we are not grouping these children + // but we are separately turning each one into a string + // (turning the non-string children into a code based on codePre and its nonStringInd) + newChildren.push( + baseStringFromChildren({ + children, + startInd: ind, + endInd: ind, + nonStringIndByChild, + format, + codePre, + }), + ); + } + + if (potentialListComponents) { + // Since we didn't change the components, + // their status of being a potential list component is not changed + newPotentialListComponents.push( + ...potentialListComponents.slice( + lastChildInd - startInd + 1, + endInd - startInd + 1, + ), + ); + } + } + + return { + newChildren, + newPotentialListComponents, + internalLists, + }; +} + +// concatenate string children and codes from non-string children +// into a single string to be parsed into a math expression +function baseStringFromChildren({ + children, + startInd, + endInd, + nonStringIndByChild, + format, + codePre, +}: { + children: any[]; + startInd: number; + endInd: number; + nonStringIndByChild: (null | number)[]; + format: "latex" | "text"; + codePre: string; +}) { + let str = ""; + + for (let ind = startInd; ind <= endInd; ind++) { + let child = children[ind]; + if (typeof child === "string") { + str += " " + child + " "; + } else { + // a non-string + let code = codePre + nonStringIndByChild[ind]; + + let nextString = returnStringForCode(format, code); + + str += nextString; + } + } + + return str; +} +function returnStringForCode(format: string, code: string) { + let nextString; + if (format === "latex") { + // for latex, must explicitly denote that code + // is a multicharacter variable + nextString = "\\operatorname{" + code + "}"; + } else { + // for text, just make sure code is surrounded by spaces + // (the presence of numbers inside code will ensure that + // it is parsed as a multicharacter variable) + nextString = " " + code + " "; + } + return nextString; +} diff --git a/packages/doenetml-worker/src/utils/text.ts b/packages/doenetml-worker/src/utils/text.ts index 3d66ab422..439ed8541 100644 --- a/packages/doenetml-worker/src/utils/text.ts +++ b/packages/doenetml-worker/src/utils/text.ts @@ -125,7 +125,7 @@ function textFromChildrenSub({ newChildren.push( childrenInRange .map(textFromComponentConverter) - .filter((v) => v.trim() !== "") + .filter((v) => v !== "") .map((v, i, a) => i === a.length - 1 ? v : v.trimEnd(), ) @@ -168,3 +168,221 @@ function textFromChildrenSub({ return { newChildren, newPotentialListComponents }; } + +export function returnTextPieceStateVariableDefinitions() { + let stateVariableDefinitions: any = {}; + + stateVariableDefinitions.numWords = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }), + definition({ + dependencyValues, + }: { + dependencyValues: { value: string }; + }) { + return { + setValue: { + numWords: dependencyValues.value.trim().split(/\s+/).length, + }, + }; + }, + }; + + stateVariableDefinitions.words = { + public: true, + shadowingInstructions: { + createComponentOfType: "text", + }, + isArray: true, + entryPrefixes: ["word"], + returnArraySizeDependencies: () => ({ + numWords: { + dependencyType: "stateVariable", + variableName: "numWords", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { numWords: number }; + }) { + return [dependencyValues.numWords]; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }; + + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + }: { + globalDependencyValues: { value: string }; + }) { + return { + setValue: { + words: globalDependencyValues.value.trim().split(/\s+/), + }, + }; + }, + }; + + stateVariableDefinitions.numCharacters = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }), + definition({ + dependencyValues, + }: { + dependencyValues: { value: string }; + }) { + // @ts-ignore + const itr = new Intl.Segmenter("en", { + granularity: "grapheme", + }).segment(dependencyValues.value); + return { + setValue: { numCharacters: [...itr].length }, + }; + }, + }; + + stateVariableDefinitions.characters = { + public: true, + shadowingInstructions: { + createComponentOfType: "text", + }, + isArray: true, + entryPrefixes: ["character"], + returnArraySizeDependencies: () => ({ + numCharacters: { + dependencyType: "stateVariable", + variableName: "numCharacters", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { numCharacters: number }; + }) { + return [dependencyValues.numCharacters]; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }; + + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + }: { + globalDependencyValues: { value: string }; + }) { + // @ts-ignore + const itr = new Intl.Segmenter("en", { + granularity: "grapheme", + }).segment(globalDependencyValues.value); + + return { + setValue: { + characters: Array.from(itr, ({ segment }) => segment), + }, + }; + }, + }; + + stateVariableDefinitions.numListItems = { + public: true, + shadowingInstructions: { + createComponentOfType: "integer", + }, + returnDependencies: () => ({ + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }), + definition({ + dependencyValues, + }: { + dependencyValues: { value: string }; + }) { + return { + setValue: { + numListItems: dependencyValues.value.trim().split(",") + .length, + }, + }; + }, + }; + + stateVariableDefinitions.listItems = { + public: true, + shadowingInstructions: { + createComponentOfType: "text", + }, + isArray: true, + entryPrefixes: ["listItem"], + returnArraySizeDependencies: () => ({ + numListItems: { + dependencyType: "stateVariable", + variableName: "numListItems", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { numListItems: number }; + }) { + return [dependencyValues.numListItems]; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + value: { + dependencyType: "stateVariable", + variableName: "value", + }, + }; + + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + }: { + globalDependencyValues: { value: string }; + }) { + return { + setValue: { + listItems: globalDependencyValues.value + .trim() + .split(",") + .map((s) => s.trim()), + }, + }; + }, + }; + + return stateVariableDefinitions; +} diff --git a/packages/doenetml/src/Viewer/renderers/textList.jsx b/packages/doenetml/src/Viewer/renderers/textList.jsx deleted file mode 100644 index 9cd7a1782..000000000 --- a/packages/doenetml/src/Viewer/renderers/textList.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import useDoenetRenderer from "../useDoenetRenderer"; - -export default React.memo(function TextList(props) { - let { name, id, SVs, children } = useDoenetRenderer(props); - - if (SVs.hidden) { - return null; - } - - if (children.length === 0 && SVs.text) { - return ( - -
- {SVs.text} - - ); - } - - let withCommas = children - .slice(1) - .reduce((a, b) => [...a, ", ", b], [children[0]]); - - return ( - - - {withCommas} - - ); -}); diff --git a/packages/utils/src/math/subset-of-reals.js b/packages/utils/src/math/subset-of-reals.js index 4cfad26be..11d1dbdfa 100644 --- a/packages/utils/src/math/subset-of-reals.js +++ b/packages/utils/src/math/subset-of-reals.js @@ -263,6 +263,11 @@ class Union extends Subset { constructor(subsets) { super(); + + if (subsets.some((s) => !s.isValid())) { + return new InvalidSet(); + } + let prelimSubsets = subsets.filter((s) => !s.isEmpty()); if (prelimSubsets.length === 0) { From 29a19b76def298f6e6853716e97519a549e9675f Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 28 Oct 2024 23:02:04 -0500 Subject: [PATCH 04/13] math list tests, further list refinements --- packages/doenetml-worker/src/Core.js | 247 ++- .../src/components/BooleanList.js | 60 +- .../doenetml-worker/src/components/Copy.js | 12 + .../doenetml-worker/src/components/Math.js | 5 +- .../src/components/MathList.js | 95 +- .../src/components/NumberList.js | 64 +- .../src/components/TextList.js | 60 +- .../src/test/tagSpecific/mathlist.test.ts | 1593 +++++++++++++++++ .../src/test/tagSpecific/sequence.test.ts | 2 - .../src/test/tagSpecific/sort.test.ts | 4 +- 10 files changed, 1918 insertions(+), 224 deletions(-) create mode 100644 packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts diff --git a/packages/doenetml-worker/src/Core.js b/packages/doenetml-worker/src/Core.js index d7d9f2cbf..5783e3930 100644 --- a/packages/doenetml-worker/src/Core.js +++ b/packages/doenetml-worker/src/Core.js @@ -2586,7 +2586,8 @@ export default class Core { if ( component.shadows && - !component.shadows.propVariable + !component.shadows.propVariable && + !component.constructor.doNotExpandAsShadowed //&& // this.componentInfoObjects.isCompositeComponent({ // componentType: component.componentType, @@ -3780,6 +3781,15 @@ export default class Core { attributeSpecification.fallBackToParentStateVariable, }; } + if ( + attributeSpecification.fallBackToSourceCompositeStateVariable + ) { + dependencies.sourceCompositeValue = { + dependencyType: "sourceCompositeStateVariable", + variableName: + attributeSpecification.fallBackToSourceCompositeStateVariable, + }; + } if (attributeSpecification.createPrimitiveOfType) { dependencies.attributePrimitive = { dependencyType: "attributePrimitive", @@ -3843,12 +3853,34 @@ export default class Core { checkForActualChange: { [varName]: true }, }; } else { - return { - useEssentialOrDefaultValue: { - [varName]: true, - }, - checkForActualChange: { [varName]: true }, - }; + // sourceCompositeValue would be undefined if fallBackToSourceCompositeStateVariable wasn't specified + // sourceCompositeValue would be null if the sourceCompositeValue state variables + // did not exist or its value was null + + let haveSourceCompositeValue = + dependencyValues.sourceCompositeValue !== + undefined && + dependencyValues.sourceCompositeValue !== null; + if ( + haveSourceCompositeValue && + !usedDefault.sourceCompositeValue && + essentialValues[varName] === undefined + ) { + return { + setValue: { + [varName]: + dependencyValues.sourceCompositeValue, + }, + checkForActualChange: { [varName]: true }, + }; + } else { + return { + useEssentialOrDefaultValue: { + [varName]: true, + }, + checkForActualChange: { [varName]: true }, + }; + } } } @@ -3910,24 +3942,49 @@ export default class Core { ], }; } else { - // no component or primitive, so value is essential and give it the desired value, but validated + let haveSourceCompositeValue = + dependencyValues.sourceCompositeValue !== + undefined && + dependencyValues.sourceCompositeValue !== null; + if ( + haveSourceCompositeValue && + !usedDefault.sourceCompositeValue && + essentialValues[varName] === undefined + ) { + // value from source composite was used, so propagate back to source composite + return { + success: true, + instructions: [ + { + setDependency: + "sourceCompositeValue", + desiredValue: + desiredStateVariableValues[ + varName + ], + }, + ], + }; + } else { + // no component or primitive, so value is essential and give it the desired value, but validated - let res = validateAttributeValue({ - value: desiredStateVariableValues[varName], - attributeSpecification, - attribute: attrName, - }); + let res = validateAttributeValue({ + value: desiredStateVariableValues[varName], + attributeSpecification, + attribute: attrName, + }); - return { - success: true, - instructions: [ - { - setEssentialValue: varName, - value: res.value, - }, - ], - sendWarnings: res.warnings, - }; + return { + success: true, + instructions: [ + { + setEssentialValue: varName, + value: res.value, + }, + ], + sendWarnings: res.warnings, + }; + } } } @@ -4309,6 +4366,13 @@ export default class Core { attributeSpecification.fallBackToParentStateVariable, }; } + if (attributeSpecification.fallBackToSourceCompositeStateVariable) { + thisDependencies.sourceCompositeValue = { + dependencyType: "sourceCompositeStateVariable", + variableName: + attributeSpecification.fallBackToSourceCompositeStateVariable, + }; + } stateVarDef.returnDependencies = () => thisDependencies; @@ -4354,12 +4418,34 @@ export default class Core { checkForActualChange: { [varName]: true }, }; } else { - return { - useEssentialOrDefaultValue: { - [varName]: true, - }, - checkForActualChange: { [varName]: true }, - }; + // sourceCompositeValue would be undefined if fallBackToSourceCompositeStateVariable wasn't specified + // sourceCompositeValue would be null if the sourceCompositeValue state variables + // did not exist or its value was null + + let haveSourceCompositeValue = + dependencyValues.sourceCompositeValue !== + undefined && + dependencyValues.sourceCompositeValue !== null; + if ( + haveSourceCompositeValue && + !usedDefault.sourceCompositeValue && + essentialValues[varName] === undefined + ) { + return { + setValue: { + [varName]: + dependencyValues.sourceCompositeValue, + }, + checkForActualChange: { [varName]: true }, + }; + } else { + return { + useEssentialOrDefaultValue: { + [varName]: true, + }, + checkForActualChange: { [varName]: true }, + }; + } } } @@ -4423,23 +4509,48 @@ export default class Core { ], }; } else { - // no component or primitive, so value is essential and give it the desired value, but validated - let res = validateAttributeValue({ - value: desiredStateVariableValues[varName], - attributeSpecification, - attribute: attrName, - }); + let haveSourceCompositeValue = + dependencyValues.sourceCompositeValue !== + undefined && + dependencyValues.sourceCompositeValue !== null; + if ( + haveSourceCompositeValue && + !usedDefault.sourceCompositeValue && + essentialValues[varName] === undefined + ) { + // value from source composite was used, so propagate back to source composite + return { + success: true, + instructions: [ + { + setDependency: + "sourceCompositeValue", + desiredValue: + desiredStateVariableValues[ + varName + ], + }, + ], + }; + } else { + // no component or primitive, so value is essential and give it the desired value, but validated + let res = validateAttributeValue({ + value: desiredStateVariableValues[varName], + attributeSpecification, + attribute: attrName, + }); - return { - success: true, - instructions: [ - { - setEssentialValue: varName, - value: res.value, - }, - ], - sendWarnings: res.warnings, - }; + return { + success: true, + instructions: [ + { + setEssentialValue: varName, + value: res.value, + }, + ], + sendWarnings: res.warnings, + }; + } } } // attribute based on child @@ -9004,7 +9115,10 @@ export default class Core { if (parent.shadowedBy) { for (let shadowingParent of parent.shadowedBy) { - if (shadowingParent.shadows.propVariable) { + if ( + shadowingParent.shadows.propVariable || + shadowingParent.constructor.doNotExpandAsShadowed + ) { continue; } @@ -9450,7 +9564,11 @@ export default class Core { let addedComponents = {}; let parentsOfDeleted = new Set(); - if (component.shadows && !component.shadows.propVariable) { + if ( + component.shadows && + !component.shadows.propVariable && + !component.constructor.doNotExpandAsShadowed + ) { // if shadows, don't update replacements // instead, replacements will get updated when shadowed component // is updated @@ -9882,7 +10000,10 @@ export default class Core { if (composite.shadowedBy) { for (let shadowingComposite of composite.shadowedBy) { - if (shadowingComposite.shadows.propVariable) { + if ( + shadowingComposite.shadows.propVariable || + shadowingComposite.constructor.doNotExpandAsShadowed + ) { continue; } @@ -9894,7 +10015,10 @@ export default class Core { let shadowingCompToDelete; if (compToDelete.shadowedBy) { for (let cShadow of compToDelete.shadowedBy) { - if (cShadow.shadows.propVariable) { + if ( + cShadow.shadows.propVariable || + cShadow.constructor.doNotExpandAsShadowed + ) { continue; } if ( @@ -10066,7 +10190,10 @@ export default class Core { if (component.shadowedBy) { for (let shadowingComponent of component.shadowedBy) { - if (shadowingComponent.shadows.propVariable) { + if ( + shadowingComponent.shadows.propVariable || + shadowingComponent.constructor.doNotExpandAsShadowed + ) { continue; } await this.processChildChangesAndRecurseToShadows( @@ -10112,7 +10239,10 @@ export default class Core { let newComponentsForShadows = {}; for (let shadowingComponent of componentToShadow.shadowedBy) { - if (shadowingComponent.shadows.propVariable) { + if ( + shadowingComponent.shadows.propVariable || + shadowingComponent.constructor.doNotExpandAsShadowed + ) { continue; } @@ -10340,7 +10470,10 @@ export default class Core { if (parentToShadow) { if (parentToShadow.shadowedBy) { for (let pShadow of parentToShadow.shadowedBy) { - if (pShadow.shadows.propVariable) { + if ( + pShadow.shadows.propVariable || + pShadow.constructor.doNotExpandAsShadowed + ) { continue; } if ( @@ -10473,7 +10606,10 @@ export default class Core { if (component.shadowedBy) { for (let shadowingComponent of component.shadowedBy) { - if (shadowingComponent.shadows.propVariable) { + if ( + shadowingComponent.shadows.propVariable || + shadowingComponent.constructor.doNotExpandAsShadowed + ) { continue; } let additionalcompositesWithAdjustedReplacements = @@ -13634,7 +13770,10 @@ function calculateAllComponentsShadowing(component) { let allShadowing = []; if (component.shadowedBy) { for (let comp2 of component.shadowedBy) { - if (!comp2.shadows.propVariable) { + if ( + !comp2.shadows.propVariable & + !comp2.constructor.doNotExpandAsShadowed + ) { allShadowing.push(comp2.componentName); let additionalShadowing = calculateAllComponentsShadowing(comp2); diff --git a/packages/doenetml-worker/src/components/BooleanList.js b/packages/doenetml-worker/src/components/BooleanList.js index 0e2dc8b1f..e58346e4e 100644 --- a/packages/doenetml-worker/src/components/BooleanList.js +++ b/packages/doenetml-worker/src/components/BooleanList.js @@ -12,6 +12,8 @@ export default class BooleanList extends CompositeComponent { static stateVariableToEvaluateAfterReplacements = "readyToExpandWhenResolved"; + static assignNamesToReplacements = true; + static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -24,6 +26,8 @@ export default class BooleanList extends CompositeComponent { // don't required composite replacements static descendantCompositesMustHaveAReplacement = false; + static doNotExpandAsShadowed = true; + static createAttributesObject() { let attributes = super.createAttributesObject(); attributes.unordered = { @@ -335,45 +339,27 @@ export default class BooleanList extends CompositeComponent { let childNameByComponent = await component.stateValues.childNameByComponent; - if (childNameByComponent.length > 0) { - for (let childName of childNameByComponent) { - let replacementSource = components[childName]; - - if (replacementSource) { - componentsCopied.push(replacementSource.componentName); + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + let childName = childNameByComponent[i]; + let replacementSource = components[childName]; - let repl = await replacementSource.serialize({ - primitiveSourceAttributesToIgnore: ["isResponse"], - }); - if (!repl.attributes) { - repl.attributes = {}; - } - Object.assign( - repl.attributes, - JSON.parse(JSON.stringify(attributesFromComposite)), - ); - replacements.push(repl); - } - } - } else { - let numComponents = await component.stateValues.numComponents; - for (let i = 0; i < numComponents; i++) { - replacements.push({ - componentType: "boolean", - attributes: JSON.parse( - JSON.stringify(attributesFromComposite), - ), - downstreamDependencies: { - [component.componentName]: [ - { - dependencyType: "referenceShadow", - compositeName: component.componentName, - propVariable: `boolean${i + 1}`, - }, - ], - }, - }); + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); } + replacements.push({ + componentType: "boolean", + attributes: JSON.parse(JSON.stringify(attributesFromComposite)), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `boolean${i + 1}`, + }, + ], + }, + }); } workspace.uniqueIdentifiersUsed = []; diff --git a/packages/doenetml-worker/src/components/Copy.js b/packages/doenetml-worker/src/components/Copy.js index 6296f7e7a..6dec195c6 100644 --- a/packages/doenetml-worker/src/components/Copy.js +++ b/packages/doenetml-worker/src/components/Copy.js @@ -1894,6 +1894,18 @@ export default class Copy extends CompositeComponent { dontSkipAttributes: ["asList"], compositeCreatesNewNamespace: newNamespace, }); + + // Since if either displayDigits or displayDecimals is supplied in the composite, + // it should override both displayDigits and displayDecimals from the source, + // we delete the attributes from the source in this special case. + // TODO: is there a more generic way to accomplish this? + if ( + attributesFromComposite.displayDigits || + attributesFromComposite.displayDecimals + ) { + delete repl.attributes.displayDigits; + delete repl.attributes.displayDecimals; + } Object.assign(repl.attributes, attributesFromComposite); } diff --git a/packages/doenetml-worker/src/components/Math.js b/packages/doenetml-worker/src/components/Math.js index cc9c25924..d4f39b8ac 100644 --- a/packages/doenetml-worker/src/components/Math.js +++ b/packages/doenetml-worker/src/components/Math.js @@ -115,6 +115,7 @@ export default class MathComponent extends InlineComponent { defaultValue: ["f", "g"], public: true, fallBackToParentStateVariable: "functionSymbols", + fallBackToSourceCompositeStateVariable: "functionSymbols", }; attributes.sourcesAreFunctionSymbols = { @@ -122,6 +123,7 @@ export default class MathComponent extends InlineComponent { createStateVariable: "sourcesAreFunctionSymbols", defaultValue: [], fallBackToParentStateVariable: "sourcesAreFunctionSymbols", + fallBackToSourceCompositeStateVariable: "sourcesAreFunctionSymbols", }; attributes.splitSymbols = { @@ -130,6 +132,7 @@ export default class MathComponent extends InlineComponent { defaultValue: true, public: true, fallBackToParentStateVariable: "splitSymbols", + fallBackToSourceCompositeStateVariable: "splitSymbols", }; attributes.parseScientificNotation = { @@ -1555,7 +1558,7 @@ function checkForLinearExpression( return { foundLinear: false }; } - // if at least one componen is a linear functions, view as linear + // if at least one component is a linear functions, view as linear result.foundLinear = true; return result; } else { diff --git a/packages/doenetml-worker/src/components/MathList.js b/packages/doenetml-worker/src/components/MathList.js index a150abf79..3f892359a 100644 --- a/packages/doenetml-worker/src/components/MathList.js +++ b/packages/doenetml-worker/src/components/MathList.js @@ -15,6 +15,8 @@ export default class MathList extends CompositeComponent { static stateVariableToEvaluateAfterReplacements = "readyToExpandWhenResolved"; + static assignNamesToReplacements = true; + static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -27,6 +29,8 @@ export default class MathList extends CompositeComponent { // don't required composite replacements static descendantCompositesMustHaveAReplacement = false; + static doNotExpandAsShadowed = true; + static createAttributesObject() { let attributes = super.createAttributesObject(); @@ -66,6 +70,7 @@ export default class MathList extends CompositeComponent { defaultValue: ["f", "g"], public: true, fallBackToParentStateVariable: "functionSymbols", + fallBackToSourceCompositeStateVariable: "functionSymbols", }; attributes.sourcesAreFunctionSymbols = { @@ -73,6 +78,7 @@ export default class MathList extends CompositeComponent { createStateVariable: "sourcesAreFunctionSymbols", defaultValue: [], fallBackToParentStateVariable: "sourcesAreFunctionSymbols", + fallBackToSourceCompositeStateVariable: "sourcesAreFunctionSymbols", }; attributes.splitSymbols = { @@ -81,6 +87,7 @@ export default class MathList extends CompositeComponent { defaultValue: true, public: true, fallBackToParentStateVariable: "splitSymbols", + fallBackToSourceCompositeStateVariable: "splitSymbols", }; attributes.parseScientificNotation = { @@ -569,63 +576,35 @@ export default class MathList extends CompositeComponent { let childInfoByComponent = await component.stateValues.childInfoByComponent; - if (childInfoByComponent.length > 0) { - for (let childInfo of childInfoByComponent) { - let replacementSource = components[childInfo.childName]; + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + let childInfo = childInfoByComponent[i]; + let replacementSource = components[childInfo.childName]; - if (replacementSource) { + if (replacementSource) { + if (childInfo.nComponents !== undefined) { + componentsCopied.push( + replacementSource.componentName + + ":" + + childInfo.component, + ); + } else { componentsCopied.push(replacementSource.componentName); - - if (childInfo.nComponents !== undefined) { - replacements.push({ - componentType: "math", - attributes: JSON.parse( - JSON.stringify(attributesFromComposite), - ), - downstreamDependencies: { - [childInfo.childName]: [ - { - dependencyType: "referenceShadow", - compositeName: component.componentName, - propVariable: `x${childInfo.component + 1}`, - }, - ], - }, - }); - } else { - let repl = await replacementSource.serialize({ - primitiveSourceAttributesToIgnore: ["isResponse"], - }); - if (!repl.attributes) { - repl.attributes = {}; - } - Object.assign( - repl.attributes, - JSON.parse(JSON.stringify(attributesFromComposite)), - ); - replacements.push(repl); - } } } - } else { - let numComponents = await component.stateValues.numComponents; - for (let i = 0; i < numComponents; i++) { - replacements.push({ - componentType: "math", - attributes: JSON.parse( - JSON.stringify(attributesFromComposite), - ), - downstreamDependencies: { - [component.componentName]: [ - { - dependencyType: "referenceShadow", - compositeName: component.componentName, - propVariable: `math${i + 1}`, - }, - ], - }, - }); - } + replacements.push({ + componentType: "math", + attributes: JSON.parse(JSON.stringify(attributesFromComposite)), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `math${i + 1}`, + }, + ], + }, + }); } workspace.uniqueIdentifiersUsed = []; @@ -675,7 +654,15 @@ export default class MathList extends CompositeComponent { let replacementSource = components[childInfo.childName]; if (replacementSource) { - componentsToCopy.push(replacementSource.componentName); + if (childInfo.nComponents !== undefined) { + componentsToCopy.push( + replacementSource.componentName + + ":" + + childInfo.component, + ); + } else { + componentsToCopy.push(replacementSource.componentName); + } } } diff --git a/packages/doenetml-worker/src/components/NumberList.js b/packages/doenetml-worker/src/components/NumberList.js index 421d8289c..b178d0b3e 100644 --- a/packages/doenetml-worker/src/components/NumberList.js +++ b/packages/doenetml-worker/src/components/NumberList.js @@ -13,6 +13,8 @@ export default class NumberList extends CompositeComponent { static stateVariableToEvaluateAfterReplacements = "readyToExpandWhenResolved"; + static assignNamesToReplacements = true; + static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -21,6 +23,12 @@ export default class NumberList extends CompositeComponent { static stateVariableToBeShadowed = "numbers"; static primaryStateVariableForDefinition = "numbersShadow"; + // even if inside a component that turned on descendantCompositesMustHaveAReplacement + // don't required composite replacements + static descendantCompositesMustHaveAReplacement = false; + + static doNotExpandAsShadowed = true; + static createAttributesObject() { let attributes = super.createAttributesObject(); @@ -342,45 +350,27 @@ export default class NumberList extends CompositeComponent { let childNameByComponent = await component.stateValues.childNameByComponent; - if (childNameByComponent.length > 0) { - for (let childName of childNameByComponent) { - let replacementSource = components[childName]; - - if (replacementSource) { - componentsCopied.push(replacementSource.componentName); + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + let childName = childNameByComponent[i]; + let replacementSource = components[childName]; - let repl = await replacementSource.serialize({ - primitiveSourceAttributesToIgnore: ["isResponse"], - }); - if (!repl.attributes) { - repl.attributes = {}; - } - Object.assign( - repl.attributes, - JSON.parse(JSON.stringify(attributesFromComposite)), - ); - replacements.push(repl); - } - } - } else { - let numComponents = await component.stateValues.numComponents; - for (let i = 0; i < numComponents; i++) { - replacements.push({ - componentType: "number", - attributes: JSON.parse( - JSON.stringify(attributesFromComposite), - ), - downstreamDependencies: { - [component.componentName]: [ - { - dependencyType: "referenceShadow", - compositeName: component.componentName, - propVariable: `number${i + 1}`, - }, - ], - }, - }); + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); } + replacements.push({ + componentType: "number", + attributes: JSON.parse(JSON.stringify(attributesFromComposite)), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `number${i + 1}`, + }, + ], + }, + }); } workspace.uniqueIdentifiersUsed = []; diff --git a/packages/doenetml-worker/src/components/TextList.js b/packages/doenetml-worker/src/components/TextList.js index 95355573c..d1613d3d1 100644 --- a/packages/doenetml-worker/src/components/TextList.js +++ b/packages/doenetml-worker/src/components/TextList.js @@ -12,6 +12,8 @@ export default class TextList extends CompositeComponent { static stateVariableToEvaluateAfterReplacements = "readyToExpandWhenResolved"; + static assignNamesToReplacements = true; + static includeBlankStringChildren = true; static removeBlankStringChildrenPostSugar = true; @@ -24,6 +26,8 @@ export default class TextList extends CompositeComponent { // don't required composite replacements static descendantCompositesMustHaveAReplacement = false; + static doNotExpandAsShadowed = true; + static createAttributesObject() { let attributes = super.createAttributesObject(); @@ -336,45 +340,27 @@ export default class TextList extends CompositeComponent { let childNameByComponent = await component.stateValues.childNameByComponent; - if (childNameByComponent.length > 0) { - for (let childName of childNameByComponent) { - let replacementSource = components[childName]; - - if (replacementSource) { - componentsCopied.push(replacementSource.componentName); + let numComponents = await component.stateValues.numComponents; + for (let i = 0; i < numComponents; i++) { + let childName = childNameByComponent[i]; + let replacementSource = components[childName]; - let repl = await replacementSource.serialize({ - primitiveSourceAttributesToIgnore: ["isResponse"], - }); - if (!repl.attributes) { - repl.attributes = {}; - } - Object.assign( - repl.attributes, - JSON.parse(JSON.stringify(attributesFromComposite)), - ); - replacements.push(repl); - } - } - } else { - let numComponents = await component.stateValues.numComponents; - for (let i = 0; i < numComponents; i++) { - replacements.push({ - componentType: "text", - attributes: JSON.parse( - JSON.stringify(attributesFromComposite), - ), - downstreamDependencies: { - [component.componentName]: [ - { - dependencyType: "referenceShadow", - compositeName: component.componentName, - propVariable: `text${i + 1}`, - }, - ], - }, - }); + if (replacementSource) { + componentsCopied.push(replacementSource.componentName); } + replacements.push({ + componentType: "text", + attributes: JSON.parse(JSON.stringify(attributesFromComposite)), + downstreamDependencies: { + [component.componentName]: [ + { + dependencyType: "referenceShadow", + compositeName: component.componentName, + propVariable: `text${i + 1}`, + }, + ], + }, + }); } workspace.uniqueIdentifiersUsed = []; diff --git a/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts new file mode 100644 index 000000000..fc74cb3f0 --- /dev/null +++ b/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts @@ -0,0 +1,1593 @@ +import { describe, expect, it, vi } from "vitest"; +import { createTestCore, returnAllStateVariables } from "../utils/test-core"; +import { + updateBooleanInputValue, + updateMathInputValue, +} from "../utils/actions"; + +const Mock = vi.fn(); +vi.stubGlobal("postMessage", Mock); + +describe("MathList tag tests", async () => { + async function test_mathList({ + core, + name, + pName, + text, + maths, + }: { + core: any; + name?: string; + pName?: string; + text?: string; + maths?: any[]; + }) { + const stateVariables = await returnAllStateVariables(core); + + if (text !== undefined && pName !== undefined) { + expect(stateVariables[pName].stateValues.text).eq(text); + } + + if (maths !== undefined && name !== undefined) { + expect( + stateVariables[name].stateValues.maths.map((x) => x.tree), + ).eqls(maths); + } + } + + it("mathList from string", async () => { + let core = await createTestCore({ + doenetML: ` +

a 1+1

+ `, + }); + + await test_mathList({ + core, + name: "/ml1", + pName: "/p", + text: "a, 1 + 1", + maths: ["a", ["+", 1, 1]], + }); + }); + + it("mathList with error in string", async () => { + let core = await createTestCore({ + doenetML: ` +

a @ 1+1

+ `, + }); + + await test_mathList({ + core, + name: "/ml1", + pName: "/p", + text: "a, _, 1 + 1", + maths: ["a", "_", ["+", 1, 1]], + }); + }); + + it("mathList in attribute containing math and number macros", async () => { + let core = await createTestCore({ + doenetML: ` +

x + 3y + 7 + 11

+

+ `, + }); + + let maths = [ + "x", + ["/", ["*", 3, "y"], 7], + 7, + -4, + 7, + -11, + 7, + "-", + 11, + ["*", 21, "x", "y"], + ["/", ["+", "x", 7], ["*", 33, "y"]], + ]; + + let stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/P"].stateValues.xs.map((x) => x.tree)).eqls( + maths, + ); + }); + + it("mathList with math children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + 1+1 +

+ +

+ a1+1 +

+ `, + }); + + let text = "a, 1 + 1"; + let maths = ["a", ["+", 1, 1]]; + + await test_mathList({ + core, + name: "/ml1", + pName: "/p1", + text, + maths, + }); + + await test_mathList({ + core, + name: "/ml2", + pName: "/p2", + text, + maths, + }); + }); + + it("mathList with math and string children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a q 1+1h +

+ `, + }); + + await test_mathList({ + core, + name: "/ml1", + pName: "/p", + text: "a, q, 1 + 1, h", + maths: ["a", "q", ["+", 1, 1], "h"], + }); + }); + + it("mathList with math and number children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + 1+1 +

+

+ a1+1 +

+ `, + }); + + let text = "a, 2"; + let maths = ["a", 2]; + + await test_mathList({ + core, + name: "/ml1", + pName: "/p1", + text, + maths, + }); + + await test_mathList({ + core, + name: "/ml2", + pName: "/p2", + text, + maths, + }); + }); + + async function test_nested_and_inverse(core: any) { + await test_mathList({ + core, + name: "/ml1", + pName: "/p", + text: "a, q, r, h, b, u, v, i, j", + maths: ["a", "q", "r", "h", "b", "u", "v", "i", "j"], + }); + await test_mathList({ + core, + name: "/ml2", + maths: ["q", "r"], + }); + await test_mathList({ + core, + name: "/ml3", + maths: ["b", "u", "v", "i", "j"], + }); + await test_mathList({ + core, + name: "/ml4", + maths: ["b", "u", "v"], + }); + await test_mathList({ + core, + name: "/ml5", + maths: ["u", "v"], + }); + await test_mathList({ + core, + name: "/ml6", + maths: ["i", "j"], + }); + + // change values + + await updateMathInputValue({ componentName: "/mi1", latex: "1", core }); + await updateMathInputValue({ componentName: "/mi2", latex: "2", core }); + await updateMathInputValue({ componentName: "/mi3", latex: "3", core }); + await updateMathInputValue({ componentName: "/mi4", latex: "4", core }); + await updateMathInputValue({ componentName: "/mi5", latex: "5", core }); + await updateMathInputValue({ componentName: "/mi6", latex: "6", core }); + await updateMathInputValue({ componentName: "/mi7", latex: "7", core }); + await updateMathInputValue({ componentName: "/mi8", latex: "8", core }); + await updateMathInputValue({ componentName: "/mi9", latex: "9", core }); + + await test_mathList({ + core, + name: "/ml1", + pName: "/p", + text: "1, 2, 3, 4, 5, 6, 7, 8, 9", + maths: [1, 2, 3, 4, 5, 6, 7, 8, 9], + }); + + await test_mathList({ + core, + name: "/ml2", + maths: [2, 3], + }); + await test_mathList({ + core, + name: "/ml3", + maths: [5, 6, 7, 8, 9], + }); + await test_mathList({ + core, + name: "/ml4", + maths: [5, 6, 7], + }); + await test_mathList({ + core, + name: "/ml5", + maths: [6, 7], + }); + await test_mathList({ + core, + name: "/ml6", + maths: [8, 9], + }); + } + + it("mathList with mathList children, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + q r + h + + + b + u v + + i j + +

+ + $ml1[1] + $ml1[2] + $ml1[3] + $ml1[4] + $ml1[5] + $ml1[6] + $ml1[7] + $ml1[8] + $ml1[9] + + `, + }); + + await test_nested_and_inverse(core); + }); + + it("mathList with mathList children and sugar, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + q r + h + + + b + u v + + i j + +

+ + $ml1[1] + $ml1[2] + $ml1[3] + $ml1[4] + $ml1[5] + $ml1[6] + $ml1[7] + $ml1[8] + $ml1[9] + `, + }); + + await test_nested_and_inverse(core); + }); + + it.skip("mathList with self references", async () => { + let core = await createTestCore({ + doenetML: ` + + p + q r + + + + + s t + + + + + + + $ml4.maths{obtainPropFromComposite name="ml7"} + + + + + + + + + + + + + + + + `, + }); + + // encodes how the different maths are actually references + // of 5 unique values + let math_to_unique = [0, 1, 2, 2, 0, 3, 4, 1, 0, 0, 3, 4]; + + async function check_list(unique_values: string[]) { + let all_values = math_to_unique.map((i) => unique_values[i]); + + await test_mathList({ + core, + name: "/ml1", + text: all_values.join(", "), + }); + } + + let unique_values = ["p", "q", "r", "s", "t"]; + + await check_list(unique_values); + + // successively enter the values "a" to "l" into the math inputs + // and test that all the components are updated + // based on the relationships in `math_to_unique` + let new_values = [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + ]; + for (let [mathInd, uniqueInd] of math_to_unique.entries()) { + // changing math `mathInd` actually corresponds to + // changing the unique value `uniqueInd` + + let val = new_values[mathInd]; + + await updateMathInputValue({ + latex: val, + componentName: `/mi${mathInd + 1}`, + core, + }); + unique_values[uniqueInd] = val; + + await check_list(unique_values); + } + }); + + it("mathList with maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + q r l k + h + + + b + u v + + i j k + +

+ `, + }); + + let vals6 = ["i", "j", "k"]; + let vals5 = ["u", "v"]; + let vals4 = ["b", ...vals5].slice(0, 2); + let vals3 = [...vals4, ...vals6].slice(0, 4); + let vals2 = ["q", "r", "l", "k"].slice(0, 2); + let vals1 = ["a", ...vals2, "h", ...vals3].slice(0, 7); + + let sub_vals = [vals2, vals3, vals4, vals5, vals6]; + + await test_mathList({ + core, + name: `/ml1`, + maths: vals1, + pName: "/p", + text: vals1.join(", "), + }); + + for (let i = 0; i < 5; i++) { + let vals = sub_vals[i]; + await test_mathList({ + core, + name: `/ml${i + 2}`, + maths: vals, + }); + } + }); + + it("copy mathList and overwrite maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

a b c d e

+

$ml1{maxNumber="3" name="ml2"}

+

$ml2{maxNumber="" name="ml3"}

+ +

a b c d e

+

$ml4{maxNumber="4" name="ml5"}

+

$ml5{maxNumber="" name="ml6"}

+ `, + }); + + let list = ["a", "b", "c", "d", "e"]; + + await test_mathList({ + core, + name: "/ml1", + maths: list, + pName: "/p1", + text: list.join(", "), + }); + await test_mathList({ + core, + name: "/ml2", + maths: list.slice(0, 3), + pName: "/p2", + text: list.slice(0, 3).join(", "), + }); + await test_mathList({ + core, + name: "/ml3", + maths: list, + pName: "/p3", + text: list.join(", "), + }); + await test_mathList({ + core, + name: "/ml4", + maths: list.slice(0, 3), + pName: "/p4", + text: list.slice(0, 3).join(", "), + }); + await test_mathList({ + core, + name: "/ml5", + maths: list.slice(0, 4), + pName: "/p5", + text: list.slice(0, 4).join(", "), + }); + await test_mathList({ + core, + name: "/ml6", + maths: list, + pName: "/p6", + text: list.join(", "), + }); + }); + + it("dynamic maximum number", async () => { + let core = await createTestCore({ + doenetML: ` + +

Maximum number 1:

+

Maximum number 2:

+
+

x y z u v w

+

$ml1{maxNumber="$mn2" name="ml2"}

+

$ml2{name="ml3"}

+

$ml3{name="ml4" maxNumber=""}

+
+
+ + `, + }); + + let list = ["x", "y", "z", "u", "v", "w"]; + + async function check_items(max1, max2) { + for (let pre of ["", "/sec2"]) { + await test_mathList({ + core, + name: `${pre}/ml1`, + maths: list.slice(0, max1), + pName: `${pre}/p1`, + text: list.slice(0, max1).join(", "), + }); + await test_mathList({ + core, + name: `${pre}/ml2`, + maths: list.slice(0, max2), + pName: `${pre}/p2`, + text: list.slice(0, max2).join(", "), + }); + await test_mathList({ + core, + name: `${pre}/ml3`, + maths: list.slice(0, max2), + pName: `${pre}/p3`, + text: list.slice(0, max2).join(", "), + }); + await test_mathList({ + core, + name: `${pre}/ml4`, + maths: list, + pName: `${pre}/p4`, + text: list.join(", "), + }); + } + } + + let max1 = 2, + max2 = Infinity; + + await check_items(max1, max2); + + max1 = Infinity; + await updateMathInputValue({ latex: "", componentName: "/mn1", core }); + await check_items(max1, max2); + + max2 = 3; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + + max1 = 4; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max1 = 1; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max2 = 10; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + }); + + it("mathList with merge math lists", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + b,c,d + e,f + g +

+

Merge math lists:

+ +

Third math: $ml1[3]

+

Fifth math: $ml1[5]

+ +

Change values: + $ml1[1] + $ml1[2] + $ml1[3] + $ml1[4] + $ml1[5] + $ml1[6] + $ml1[7] +

+ `, + }); + + async function check_items(vals: (string | string[])[]) { + let maths = vals.map((v) => + Array.isArray(v) ? ["list", ...v] : v, + ); + + let texts = vals.map((v) => (Array.isArray(v) ? v.join(", ") : v)); + + await test_mathList({ + core, + name: `/ml1`, + maths, + pName: `/p1`, + text: texts.join(", "), + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + `Third math: ${texts[2]}`, + ); + expect(stateVariables["/p3"].stateValues.text).eq( + `Fifth math: ${texts[4] ?? ""}`, + ); + } + + let vals = ["a", ["b", "c", "d"], ["e", "f"], "g"]; + await check_items(vals); + + vals = ["h", ["b", "c", "i"], ["e", "j"], "k"]; + + for (let [i, v] of vals.entries()) { + await updateMathInputValue({ + latex: v.toString(), + componentName: `/mi${i + 1}`, + core, + }); + } + await check_items(vals); + + await updateBooleanInputValue({ + boolean: true, + componentName: "/merge", + core, + }); + vals = ["h", "b", "c", "i", "e", "j", "k"]; + await check_items(vals); + + vals = ["l", "m", "n", "o", "p", "q", "r"]; + for (let [i, v] of vals.entries()) { + await updateMathInputValue({ + latex: v.toString(), + componentName: `/mi${i + 1}`, + core, + }); + } + await check_items(vals); + + await updateBooleanInputValue({ + boolean: false, + componentName: "/merge", + core, + }); + vals = ["l", ["m", "n", "o"], ["p", "q"], "r"]; + await check_items(vals); + }); + + it("always merge math lists when have one math child", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a,b,c,d,e +

+ +

Third math: $ml1[3]

+

Fifth math: $ml1[5]

+ +

Change values: + $ml1[1] + $ml1[2] + $ml1[3] + $ml1[4] + $ml1[5] +

+ `, + }); + + async function check_items(vals: string[]) { + await test_mathList({ + core, + name: `/ml1`, + maths: vals, + pName: `/p1`, + text: vals.join(", "), + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + `Third math: ${vals[2]}`, + ); + expect(stateVariables["/p3"].stateValues.text).eq( + `Fifth math: ${vals[4]}`, + ); + } + + let vals = ["a", "b", "c", "d", "e"]; + + await check_items(vals); + + vals = ["f", "g", "h", "i", "j"]; + for (let [i, v] of vals.entries()) { + await updateMathInputValue({ + latex: v.toString(), + componentName: `/mi${i + 1}`, + core, + }); + } + await check_items(vals); + }); + + it("maxNumber with when have one math child", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a,b,c,d,e +

+ +

Copied math: $m

+ +

Copied mathList: $ml

+ + `, + }); + + let vals = ["a", "b", "c", "d", "e"]; + + await test_mathList({ + core, + name: `/ml`, + maths: vals.slice(0, 3), + pName: `/p1`, + text: vals.slice(0, 3).join(", "), + }); + + let stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p2"].stateValues.text).eq( + `Copied math: ${vals.join(", ")}`, + ); + expect(stateVariables["/p3"].stateValues.text).eq( + `Copied mathList: ${vals.slice(0, 3).join(", ")}`, + ); + }); + + it("maxNumber with merge math lists", async () => { + let core = await createTestCore({ + doenetML: ` + +

+ 1 + 2, 345 6 + 7,8,9 + 10 11 + 12, 13, 14, 15 + 16 17 18 19 20 +

+

Merge math lists:

+

Maximum number:

+ + +

$ml{name="mlCopy"}

+ `, + }); + + let vals_merge = [...Array(20).keys()].map((x) => x + 1); + + let vals_no_merge = [ + 1, + 2, + 3, + 4, + 5, + 6, + [7, 8, 9], + 10, + 11, + [12, 13, 14, 15], + 16, + 17, + 18, + 19, + 20, + ]; + + async function check_items(mml: boolean, maxNum: number) { + if (mml) { + let vals = vals_merge.slice(0, maxNum); + await test_mathList({ + core, + name: "/ml", + maths: vals, + pName: "/p", + text: vals.join(", "), + }); + await test_mathList({ + core, + name: "/mlCopy", + maths: vals, + pName: "/pCopy", + text: vals.join(", "), + }); + } else { + let vals = vals_no_merge + .slice(0, maxNum) + .map((v) => (Array.isArray(v) ? ["list", ...v] : v)); + let texts = vals.map((v) => + Array.isArray(v) ? v.slice(1).join(", ") : v, + ); + + await test_mathList({ + core, + name: "/ml", + maths: vals, + pName: "/p", + text: texts.join(", "), + }); + await test_mathList({ + core, + name: "/mlCopy", + maths: vals, + pName: "/pCopy", + text: texts.join(", "), + }); + } + } + + let maxNum = 3; + let mml = false; + + await check_items(mml, maxNum); + + maxNum = 6; + await updateMathInputValue({ + latex: maxNum.toString(), + componentName: "/maxNum", + core, + }); + await check_items(mml, maxNum); + + maxNum = 7; + await updateMathInputValue({ + latex: maxNum.toString(), + componentName: "/maxNum", + core, + }); + await check_items(mml, maxNum); + + mml = true; + await updateBooleanInputValue({ + boolean: mml, + componentName: "/mml", + core, + }); + await check_items(mml, maxNum); + + maxNum = 13; + await updateMathInputValue({ + latex: maxNum.toString(), + componentName: "/maxNum", + core, + }); + await check_items(mml, maxNum); + + mml = false; + await updateBooleanInputValue({ + boolean: mml, + componentName: "/mml", + core, + }); + await check_items(mml, maxNum); + }); + + it("maxNumber with mathList or numberList child", async () => { + let core = await createTestCore({ + doenetML: ` + + + +

1 2 3

+

1 2 3

+

1 2 3

+ +

$ml{name="mlCopy"}

+

$mlMl{name="mlMlCopy"}

+

$mlNl{name="mlNlCopy"}

+ `, + }); + + let names = [ + ["/ml", "/pMl"], + ["/mlMl", "/pMlMl"], + ["/mlNl", "/pMlNl"], + ["/mlCopy", "/pCopyMl"], + ["/mlMlCopy", "/pCopyMlMl"], + ["/mlNlCopy", "/pCopyMlNl"], + ]; + async function check_items(maxN: number) { + let maths = [1, 2, 3].slice(0, maxN); + let text = maths.join(", "); + + for (let [m, p] of names) { + await test_mathList({ + core, + name: m, + maths, + pName: p, + text, + }); + } + } + + let maxN = 2; + await check_items(maxN); + + maxN = 4; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + + maxN = 1; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + }); + + it("mathList within mathLists, ignore child hide", async () => { + let core = await createTestCore({ + doenetML: ` +

a b c

+ +

+ x + $ml1 + y + $ml1{hide="false"} +

+ +

$ml2{name="ml3" maxNumber="6"}

+ + `, + }); + + await test_mathList({ + core, + name: "/ml2", + maths: ["x", "a", "b", "c", "y", "a", "b", "c"], + pName: "/p2", + text: "x, a, b, c, y, a, b, c", + }); + + await test_mathList({ + core, + name: "/ml3", + maths: ["x", "a", "b", "c", "y", "a"], + pName: "/p3", + text: "x, a, b, c, y, a", + }); + }); + + it("mathList does not force composite replacement, even in boolean", async () => { + let core = await createTestCore({ + doenetML: ` + + $nothing x = x + + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/b"].stateValues.value).eq(true); + }); + + it("assignNames", async () => { + let core = await createTestCore({ + doenetML: ` +

x y z

+

$a, $b, $c

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p1"].stateValues.text).eq("x, y, z"); + expect(stateVariables["/p2"].stateValues.text).eq("x, y, z"); + expect(stateVariables["/a"].stateValues.value.tree).eq("x"); + expect(stateVariables["/a"].stateValues.value.tree).eq("x"); + expect(stateVariables["/b"].stateValues.value.tree).eq("y"); + expect(stateVariables["/c"].stateValues.value.tree).eq("z"); + }); + + it("functionSymbols", async () => { + let core = await createTestCore({ + doenetML: ` +

f(x) h(x) a(x)

+

f(x) h(x) a(x)

+

+ h(x) g(x) a(x) +

+ `, + }); + + await test_mathList({ + core, + name: "/mlDefault", + maths: [ + ["apply", "f", "x"], + ["*", "h", "x"], + ["*", "a", "x"], + ], + pName: "/pDefault", + text: "f(x), h x, a x", + }); + await test_mathList({ + core, + name: "/mlH", + maths: [ + ["*", "f", "x"], + ["apply", "h", "x"], + ["*", "a", "x"], + ], + pName: "/pH", + text: "f x, h(x), a x", + }); + await test_mathList({ + core, + name: "/mlMixed", + maths: [ + ["*", "h", "x"], + ["apply", "g", "x"], + ["apply", "a", "x"], + ], + pName: "/pMixed", + text: "h x, g(x), a(x)", + }); + }); + + it("sourcesAreFunctionSymbols", async () => { + let core = await createTestCore({ + doenetML: ` + + f + g + h + a + +

$fun1(x) $fun3(x) $fun4(x)

+

$fun1(x) $fun3(x) $fun4(x)

+

+ $fun3(x) $fun2(x) $fun4(x) +

+ `, + }); + + await test_mathList({ + core, + name: "/mlDefault", + maths: [ + ["*", "f", "x"], + ["*", "h", "x"], + ["*", "a", "x"], + ], + pName: "/pDefault", + text: "f x, h x, a x", + }); + await test_mathList({ + core, + name: "/mlH", + maths: [ + ["*", "f", "x"], + ["apply", "h", "x"], + ["*", "a", "x"], + ], + pName: "/pH", + text: "f x, h(x), a x", + }); + await test_mathList({ + core, + name: "/mlMixed", + maths: [ + ["*", "h", "x"], + ["apply", "g", "x"], + ["apply", "a", "x"], + ], + pName: "/pMixed", + text: "h x, g(x), a(x)", + }); + }); + + it("splitSymbols", async () => { + let core = await createTestCore({ + doenetML: ` +

xy yz

+

xy yz

+

+ xy yz zx +

+ `, + }); + + await test_mathList({ + core, + name: "/mlDefault", + maths: [ + ["*", "x", "y"], + ["*", "y", "z"], + ], + pName: "/pDefault", + text: "x y, y z", + }); + await test_mathList({ + core, + name: "/mlN", + maths: ["xy", "yz"], + pName: "/pN", + text: "xy, yz", + }); + await test_mathList({ + core, + name: "/mlMixed", + maths: ["xy", ["*", "y", "z"], "zx"], + pName: "/pMixed", + text: "xy, y z, zx", + }); + }); + + it("parseScientificNotation", async () => { + let core = await createTestCore({ + doenetML: ` +

1E-12 3E2

+

1E-12 3E2

+ `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/pDefault"].stateValues.text).eq( + "1 E - 12, 3 E2", + ); + expect(stateVariables["/pP"].stateValues.text).eq("1 * 10⁻¹², 300"); + }); + + it("mathList and rounding, from strings", async () => { + let core = await createTestCore({ + doenetML: ` +

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+ +

+

+

+

+

+

+ +

+

+

+

+

+

+ + + `, + }); + + let vals = [ + 2345.1535268, 3.52343, 0.5, 0.00000000000052523, + 0.000000000000000000006, + ]; + let text1 = ["2345", "3.523", "0.5", "5.252 * 10⁻¹³", "0"].join(", "); + let text2 = ["2345", "3.523", "0.5000", "5.252 * 10⁻¹³", "0.000"].join( + ", ", + ); + let text3 = ["2345.154", "3.523", "0.5", "0", "0"].join(", "); + let text4 = ["2345.154", "3.523", "0.500", "0.000", "0.000"].join(", "); + let text5 = [ + "2345.1535", + "3.5234", + "0.5", + "5.25 * 10⁻¹³", + "6 * 10⁻²¹", + ].join(", "); + let text6 = [ + "2345.1535", + "3.5234", + "0.5000", + "5.25 * 10⁻¹³", + "6.00 * 10⁻²¹", + ].join(", "); + + for (let post of ["", "a", "b"]) { + await test_mathList({ + core, + name: `/ml1${post}`, + maths: vals, + pName: `/p1${post}`, + text: text1, + }); + await test_mathList({ + core, + name: `/ml2${post}`, + maths: vals, + pName: `/p2${post}`, + text: text2, + }); + await test_mathList({ + core, + name: `/ml3${post}`, + maths: vals, + pName: `/p3${post}`, + text: text3, + }); + await test_mathList({ + core, + name: `/ml4${post}`, + maths: vals, + pName: `/p4${post}`, + text: text4, + }); + await test_mathList({ + core, + name: `/ml5${post}`, + maths: vals, + pName: `/p5${post}`, + text: text5, + }); + await test_mathList({ + core, + name: `/ml6${post}`, + maths: vals, + pName: `/p6${post}`, + text: text6, + }); + } + }); + + it("mathList and rounding, ignore math and number children attributes", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+ +

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+ `, + }); + let vals = [ + 2345.1535268, 3.52343, 5, 0.00000000000000052523, + 0.000000000000000000006, + ]; + let text1 = ["2345.15", "3.52", "5", "0", "0"].join(", "); + let text2 = ["2345", "3.523", "5", "0", "0"].join(", "); + let text3 = ["2345", "3.523", "5.000", "0.000", "0.000"].join(", "); + let text4 = ["2345.1535", "3.5234", "5", "0", "0"].join(", "); + let text5 = ["2345.1535", "3.5234", "5.0000", "0.0000", "0.0000"].join( + ", ", + ); + let text6 = ["2345", "3.523", "5", "5.252 * 10⁻¹⁶", "6 * 10⁻²¹"].join( + ", ", + ); + + for (let post of ["", "n"]) { + await test_mathList({ + core, + name: `/ml1${post}`, + maths: vals, + pName: `/p1${post}`, + text: text1, + }); + await test_mathList({ + core, + name: `/ml2${post}`, + maths: vals, + pName: `/p2${post}`, + text: text2, + }); + await test_mathList({ + core, + name: `/ml3${post}`, + maths: vals, + pName: `/p3${post}`, + text: text3, + }); + await test_mathList({ + core, + name: `/ml4${post}`, + maths: vals, + pName: `/p4${post}`, + text: text4, + }); + await test_mathList({ + core, + name: `/ml5${post}`, + maths: vals, + pName: `/p5${post}`, + text: text5, + }); + await test_mathList({ + core, + name: `/ml6${post}`, + maths: vals, + pName: `/p6${post}`, + text: text6, + }); + } + }); + + it("mathList and rounding, copy and override", async () => { + let core = await createTestCore({ + doenetML: ` +

34.245023482352345 0.0023823402358234234

+

+

+

+

+ `, + }); + + let vals = [34.245023482352345, 0.0023823402358234234]; + + let text = ["34.25", "0.00238"].join(", "); + let textDig6 = ["34.245", "0.00238234"].join(", "); + let textDec6 = ["34.245023", "0.002382"].join(", "); + + await test_mathList({ + core, + name: `/ml`, + maths: vals, + pName: `/p`, + text: text, + }); + await test_mathList({ + core, + name: `/mlDig6`, + maths: vals, + pName: `/pDig6`, + text: textDig6, + }); + await test_mathList({ + core, + name: `/mlDig6a`, + maths: vals, + pName: `/pDig6a`, + text: textDig6, + }); + await test_mathList({ + core, + name: `/mlDec6`, + maths: vals, + pName: `/pDec6`, + text: textDec6, + }); + await test_mathList({ + core, + name: `/mlDec6a`, + maths: vals, + pName: `/pDec6a`, + text: textDec6, + }); + }); + + it("mathList adapts to math and text", async () => { + let core = await createTestCore({ + doenetML: ` + a bc + +

Math list as math: $ml

+

Math list as text: $ml

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/m"].stateValues.value.tree).eqls([ + "list", + "a", + "b", + "c", + ]); + expect(stateVariables["/t"].stateValues.value).eq("a, b, c"); + }); + + it("mathList adapts to numberList", async () => { + let core = await createTestCore({ + doenetML: ` +

9 87

+ +

$ml

+

Change second number: $nl[2]

+ +

Change 1st and 3rd number via point: ($nl[1],$nl[3])

+ + `, + }); + + async function test_items(n1: number, n2: number, n3: number) { + let text = [n1, n2, n3].join(", "); + await test_mathList({ + core, + name: "/ml", + maths: [n1, n2, n3], + pName: "/p1", + text, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/p2"].stateValues.text).eq(text); + } + + let n1 = 9, + n2 = 8, + n3 = 7; + + await test_items(n1, n2, n3); + + n2 = 83; + await updateMathInputValue({ + latex: n2.toString(), + componentName: "/mi1", + core, + }); + await test_items(n1, n2, n3); + + n1 = -1; + n3 = 2; + await updateMathInputValue({ + latex: `(${n1}, ${n3})`, + componentName: "/mi2", + core, + }); + await test_items(n1, n2, n3); + }); + + it("text and latex from mathList", async () => { + let core = await createTestCore({ + doenetML: ` + x/y a^b + +

Text: $ml.text

+

Latex: $ml.latex

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/pText"].stateValues.text).eq("Text: x/y, a^b"); + expect(stateVariables["/pLatex"].stateValues.text).eq( + "Latex: \\frac{x}{y}, a^{b}", + ); + }); +}); diff --git a/packages/doenetml-worker/src/test/tagSpecific/sequence.test.ts b/packages/doenetml-worker/src/test/tagSpecific/sequence.test.ts index 06ed1b6eb..2de1fc868 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/sequence.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/sequence.test.ts @@ -1,10 +1,8 @@ import { describe, expect, it, vi } from "vitest"; import { createTestCore, returnAllStateVariables } from "../utils/test-core"; -import { cleanLatex } from "../utils/math"; import { updateBooleanInputValue, updateMathInputValue, - updateMatrixInputValue, updateTextInputValue, } from "../utils/actions"; import me from "math-expressions"; diff --git a/packages/doenetml-worker/src/test/tagSpecific/sort.test.ts b/packages/doenetml-worker/src/test/tagSpecific/sort.test.ts index 5a95ceede..ba7b59d24 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/sort.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/sort.test.ts @@ -131,10 +131,10 @@ describe("Sort tag tests", async () => { let core = await createTestCore({ doenetML: `

- + - sqrt(2)10 + sqrt(2)10 2 3 From 1fadc8702859463d97561e0a96f7bda4a3c74838 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 00:04:34 -0500 Subject: [PATCH 05/13] numberList tests --- .../src/test/tagSpecific/mathlist.test.ts | 1 - .../src/test/tagSpecific/numberlist.test.ts | 1045 ++++ .../cypress/e2e/tagSpecific/mathlist.cy.js | 5197 ----------------- .../cypress/e2e/tagSpecific/numberlist.cy.js | 1669 ------ 4 files changed, 1045 insertions(+), 6867 deletions(-) create mode 100644 packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts delete mode 100644 packages/test-cypress/cypress/e2e/tagSpecific/mathlist.cy.js delete mode 100644 packages/test-cypress/cypress/e2e/tagSpecific/numberlist.cy.js diff --git a/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts index fc74cb3f0..5b5549cd0 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/mathlist.test.ts @@ -1042,7 +1042,6 @@ describe("MathList tag tests", async () => { expect(stateVariables["/p1"].stateValues.text).eq("x, y, z"); expect(stateVariables["/p2"].stateValues.text).eq("x, y, z"); expect(stateVariables["/a"].stateValues.value.tree).eq("x"); - expect(stateVariables["/a"].stateValues.value.tree).eq("x"); expect(stateVariables["/b"].stateValues.value.tree).eq("y"); expect(stateVariables["/c"].stateValues.value.tree).eq("z"); }); diff --git a/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts new file mode 100644 index 000000000..66fffbce6 --- /dev/null +++ b/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts @@ -0,0 +1,1045 @@ +import { describe, expect, it, vi } from "vitest"; +import { createTestCore, returnAllStateVariables } from "../utils/test-core"; +import { updateMathInputValue } from "../utils/actions"; + +const Mock = vi.fn(); +vi.stubGlobal("postMessage", Mock); + +describe("NumberList tag tests", async () => { + async function test_numberList({ + core, + name, + pName, + text, + numbers, + }: { + core: any; + name?: string; + pName?: string; + text?: string; + numbers?: any[]; + }) { + const stateVariables = await returnAllStateVariables(core); + + if (text !== undefined && pName !== undefined) { + expect(stateVariables[pName].stateValues.text).eq(text); + } + + if (numbers !== undefined && name !== undefined) { + expect(stateVariables[name].stateValues.numbers).eqls(numbers); + } + } + + it("numberList from string", async () => { + let core = await createTestCore({ + doenetML: ` +

5 1+1

+ `, + }); + + await test_numberList({ + core, + name: "/nl1", + pName: "/p", + text: "5, 2", + numbers: [5, 2], + }); + }); + + it("numberList with error in string", async () => { + let core = await createTestCore({ + doenetML: ` +

5 @ 1+1

+ `, + }); + + await test_numberList({ + core, + name: "/nl1", + pName: "/p", + text: "5, NaN, 2", + numbers: [5, NaN, 2], + }); + }); + + it("numberList with number children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 5 + 1+1 +

+ +

+ 51+1 +

+ `, + }); + + let text = "5, 2"; + let numbers = [5, 2]; + + await test_numberList({ + core, + name: "/nl1", + pName: "/p1", + text, + numbers, + }); + + await test_numberList({ + core, + name: "/nl2", + pName: "/p2", + text, + numbers, + }); + }); + + it("numberList with number and string children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ -1 8/2 + 5 9 1+1 +

+ `, + }); + + await test_numberList({ + core, + name: "/nl1", + pName: "/p", + text: "-1, 4, 5, 9, 2", + numbers: [-1, 4, 5, 9, 2], + }); + }); + + it("numberList with math and number children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 5 + 1+1 +

+ + `, + }); + + let text = "5, 2"; + let numbers = [5, 2]; + + await test_numberList({ + core, + name: "/nl1", + pName: "/p1", + text, + numbers, + }); + }); + + async function test_nested_and_inverse(core: any) { + await test_numberList({ + core, + name: "/nl1", + pName: "/p", + text: "1, 2, 3, 4, 5, 6, 7, 8, 9", + numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9], + }); + + await test_numberList({ + core, + name: "/nl2", + numbers: [2, 3], + }); + await test_numberList({ + core, + name: "/nl3", + numbers: [5, 6, 7, 8, 9], + }); + await test_numberList({ + core, + name: "/nl4", + numbers: [5, 6, 7], + }); + await test_numberList({ + core, + name: "/nl5", + numbers: [6, 7], + }); + await test_numberList({ + core, + name: "/nl6", + numbers: [8, 9], + }); + + // change values + + await updateMathInputValue({ + componentName: "/mi1", + latex: "-11", + core, + }); + await updateMathInputValue({ + componentName: "/mi2", + latex: "-12", + core, + }); + await updateMathInputValue({ + componentName: "/mi3", + latex: "-13", + core, + }); + await updateMathInputValue({ + componentName: "/mi4", + latex: "-14", + core, + }); + await updateMathInputValue({ + componentName: "/mi5", + latex: "-15", + core, + }); + await updateMathInputValue({ + componentName: "/mi6", + latex: "-16", + core, + }); + await updateMathInputValue({ + componentName: "/mi7", + latex: "-17", + core, + }); + await updateMathInputValue({ + componentName: "/mi8", + latex: "-18", + core, + }); + await updateMathInputValue({ + componentName: "/mi9", + latex: "-19", + core, + }); + + await test_numberList({ + core, + name: "/nl1", + pName: "/p", + text: "-11, -12, -13, -14, -15, -16, -17, -18, -19", + numbers: [-11, -12, -13, -14, -15, -16, -17, -18, -19], + }); + + await test_numberList({ + core, + name: "/nl2", + numbers: [-12, -13], + }); + await test_numberList({ + core, + name: "/nl3", + numbers: [-15, -16, -17, -18, -19], + }); + await test_numberList({ + core, + name: "/nl4", + numbers: [-15, -16, -17], + }); + await test_numberList({ + core, + name: "/nl5", + numbers: [-16, -17], + }); + await test_numberList({ + core, + name: "/nl6", + numbers: [-18, -19], + }); + } + + it("numberList with numberList children, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 1 + 2 3 + 4 + + + 5 + 6 7 + + 8 9 + +

+ + $nl1[1] + $nl1[2] + $nl1[3] + $nl1[4] + $nl1[5] + $nl1[6] + $nl1[7] + $nl1[8] + $nl1[9] + + `, + }); + + await test_nested_and_inverse(core); + }); + + it("numberList with numberList children and sugar, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 1 + 2 3 + 4 + + + 5 + 6 7 + + 8 9 + +

+ + $nl1[1] + $nl1[2] + $nl1[3] + $nl1[4] + $nl1[5] + $nl1[6] + $nl1[7] + $nl1[8] + $nl1[9] + `, + }); + + await test_nested_and_inverse(core); + }); + + it("numberList with maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 1 + 2 3 4 5 + 6 + + + 7 + 8 9 + + 10 11 12 + +

+ `, + }); + + let vals6 = [10, 11, 12]; + let vals5 = [8, 9]; + let vals4 = [7, ...vals5].slice(0, 2); + let vals3 = [...vals4, ...vals6].slice(0, 4); + let vals2 = [2, 3, 4, 5].slice(0, 2); + let vals1 = [1, ...vals2, 6, ...vals3].slice(0, 7); + + let sub_vals = [vals2, vals3, vals4, vals5, vals6]; + + await test_numberList({ + core, + name: `/nl1`, + numbers: vals1, + pName: "/p", + text: vals1.join(", "), + }); + + for (let i = 0; i < 5; i++) { + let vals = sub_vals[i]; + await test_numberList({ + core, + name: `/nl${i + 2}`, + numbers: vals, + }); + } + }); + + it("copy numberList and overwrite maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

1 2 3 4 5

+

$nl1{maxNumber="3" name="nl2"}

+

$nl2{maxNumber="" name="nl3"}

+ +

1 2 3 4 5

+

$nl4{maxNumber="4" name="nl5"}

+

$nl5{maxNumber="" name="nl6"}

+ `, + }); + + let list = [1, 2, 3, 4, 5]; + + await test_numberList({ + core, + name: "/nl1", + numbers: list, + pName: "/p1", + text: list.join(", "), + }); + await test_numberList({ + core, + name: "/nl2", + numbers: list.slice(0, 3), + pName: "/p2", + text: list.slice(0, 3).join(", "), + }); + await test_numberList({ + core, + name: "/nl3", + numbers: list, + pName: "/p3", + text: list.join(", "), + }); + await test_numberList({ + core, + name: "/nl4", + numbers: list.slice(0, 3), + pName: "/p4", + text: list.slice(0, 3).join(", "), + }); + await test_numberList({ + core, + name: "/nl5", + numbers: list.slice(0, 4), + pName: "/p5", + text: list.slice(0, 4).join(", "), + }); + await test_numberList({ + core, + name: "/nl6", + numbers: list, + pName: "/p6", + text: list.join(", "), + }); + }); + + it("dynamic maximum number", async () => { + let core = await createTestCore({ + doenetML: ` + +

Maximum number 1:

+

Maximum number 2:

+
+

1 2 3 4 5 6

+

$nl1{maxNumber="$mn2" name="nl2"}

+

$nl2{name="nl3"}

+

$nl3{name="nl4" maxNumber=""}

+
+
+ + `, + }); + + let list = [1, 2, 3, 4, 5, 6]; + + async function check_items(max1, max2) { + for (let pre of ["", "/sec2"]) { + await test_numberList({ + core, + name: `${pre}/nl1`, + numbers: list.slice(0, max1), + pName: `${pre}/p1`, + text: list.slice(0, max1).join(", "), + }); + await test_numberList({ + core, + name: `${pre}/nl2`, + numbers: list.slice(0, max2), + pName: `${pre}/p2`, + text: list.slice(0, max2).join(", "), + }); + await test_numberList({ + core, + name: `${pre}/nl3`, + numbers: list.slice(0, max2), + pName: `${pre}/p3`, + text: list.slice(0, max2).join(", "), + }); + await test_numberList({ + core, + name: `${pre}/nl4`, + numbers: list, + pName: `${pre}/p4`, + text: list.join(", "), + }); + } + } + + let max1 = 2, + max2 = Infinity; + + await check_items(max1, max2); + + max1 = Infinity; + await updateMathInputValue({ latex: "", componentName: "/mn1", core }); + await check_items(max1, max2); + + max2 = 3; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + + max1 = 4; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max1 = 1; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max2 = 10; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + }); + + it("maxNumber with mathList or numberList child", async () => { + let core = await createTestCore({ + doenetML: ` + + + +

1 2 3

+

1 2 3

+

1 2 3

+ +

$nl{name="nlCopy"}

+

$nlNl{name="nlNlCopy"}

+

$nlMl{name="nlMlCopy"}

+ `, + }); + + let names = [ + ["/nl", "/pNl"], + ["/nlNl", "/pNlNl"], + ["/nlMl", "/pNlMl"], + ["/nlCopy", "/pCopyMl"], + ["/nlNlCopy", "/pCopyNlNl"], + ["/nlMlCopy", "/pCopyNlMl"], + ]; + async function check_items(maxN: number) { + let numbers = [1, 2, 3].slice(0, maxN); + let text = numbers.join(", "); + + for (let [m, p] of names) { + await test_numberList({ + core, + name: m, + numbers, + pName: p, + text, + }); + } + } + + let maxN = 2; + await check_items(maxN); + + maxN = 4; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + + maxN = 1; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + }); + + it("numberList within numberLists, ignore child hide", async () => { + let core = await createTestCore({ + doenetML: ` +

1 2 3

+ +

+ 4 + $nl1 + 5 + $nl1{hide="false"} +

+ +

$nl2{name="nl3" maxNumber="6"}

+ + `, + }); + + await test_numberList({ + core, + name: "/nl2", + numbers: [4, 1, 2, 3, 5, 1, 2, 3], + pName: "/p2", + text: "4, 1, 2, 3, 5, 1, 2, 3", + }); + + await test_numberList({ + core, + name: "/nl3", + numbers: [4, 1, 2, 3, 5, 1], + pName: "/p3", + text: "4, 1, 2, 3, 5, 1", + }); + }); + + it("numberList does not force composite replacement, even in boolean", async () => { + let core = await createTestCore({ + doenetML: ` + + $nothing 3 = 3 + + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/b"].stateValues.value).eq(true); + }); + + it("assignNames", async () => { + let core = await createTestCore({ + doenetML: ` +

1 2 3

+

$a, $b, $c

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p1"].stateValues.text).eq("1, 2, 3"); + expect(stateVariables["/p2"].stateValues.text).eq("1, 2, 3"); + expect(stateVariables["/a"].stateValues.value).eq(1); + expect(stateVariables["/b"].stateValues.value).eq(2); + expect(stateVariables["/c"].stateValues.value).eq(3); + }); + + it("numberList and rounding, from strings", async () => { + let core = await createTestCore({ + doenetML: ` +

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

+ +

+

+

+

+

+

+ +

+

+

+

+

+

+ + + `, + }); + + let vals = [ + 2345.1535268, 3.52343, 0.5, 0.00000000000052523, + 0.000000000000000000006, + ]; + let text1 = ["2345", "3.523", "0.5", "5.252 * 10^(-13)", "0"].join( + ", ", + ); + let text2 = [ + "2345", + "3.523", + "0.5000", + "5.252 * 10^(-13)", + "0.000", + ].join(", "); + let text3 = ["2345.154", "3.523", "0.5", "0", "0"].join(", "); + let text4 = ["2345.154", "3.523", "0.500", "0.000", "0.000"].join(", "); + let text5 = [ + "2345.1535", + "3.5234", + "0.5", + "5.25 * 10^(-13)", + "6 * 10^(-21)", + ].join(", "); + let text6 = [ + "2345.1535", + "3.5234", + "0.5000", + "5.25 * 10^(-13)", + "6.00 * 10^(-21)", + ].join(", "); + + for (let post of ["", "a", "b"]) { + await test_numberList({ + core, + name: `/nl1${post}`, + numbers: vals, + pName: `/p1${post}`, + text: text1, + }); + await test_numberList({ + core, + name: `/nl2${post}`, + numbers: vals, + pName: `/p2${post}`, + text: text2, + }); + await test_numberList({ + core, + name: `/nl3${post}`, + numbers: vals, + pName: `/p3${post}`, + text: text3, + }); + await test_numberList({ + core, + name: `/nl4${post}`, + numbers: vals, + pName: `/p4${post}`, + text: text4, + }); + await test_numberList({ + core, + name: `/nl5${post}`, + numbers: vals, + pName: `/p5${post}`, + text: text5, + }); + await test_numberList({ + core, + name: `/nl6${post}`, + numbers: vals, + pName: `/p6${post}`, + text: text6, + }); + } + }); + + it("numberList and rounding, ignore math and number children attributes", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+ +

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+

+ 2345.1535268 + 3.52343 + 5 + 0.00000000000000052523 + 0.000000000000000000006 +

+ `, + }); + let vals = [ + 2345.1535268, 3.52343, 5, 0.00000000000000052523, + 0.000000000000000000006, + ]; + let text1 = ["2345.15", "3.52", "5", "0", "0"].join(", "); + let text2 = ["2345", "3.523", "5", "0", "0"].join(", "); + let text3 = ["2345", "3.523", "5.000", "0.000", "0.000"].join(", "); + let text4 = ["2345.1535", "3.5234", "5", "0", "0"].join(", "); + let text5 = ["2345.1535", "3.5234", "5.0000", "0.0000", "0.0000"].join( + ", ", + ); + let text6 = [ + "2345", + "3.523", + "5", + "5.252 * 10^(-16)", + "6 * 10^(-21)", + ].join(", "); + + for (let post of ["", "m"]) { + await test_numberList({ + core, + name: `/nl1${post}`, + numbers: vals, + pName: `/p1${post}`, + text: text1, + }); + await test_numberList({ + core, + name: `/nl2${post}`, + numbers: vals, + pName: `/p2${post}`, + text: text2, + }); + await test_numberList({ + core, + name: `/nl3${post}`, + numbers: vals, + pName: `/p3${post}`, + text: text3, + }); + await test_numberList({ + core, + name: `/nl4${post}`, + numbers: vals, + pName: `/p4${post}`, + text: text4, + }); + await test_numberList({ + core, + name: `/nl5${post}`, + numbers: vals, + pName: `/p5${post}`, + text: text5, + }); + await test_numberList({ + core, + name: `/nl6${post}`, + numbers: vals, + pName: `/p6${post}`, + text: text6, + }); + } + }); + + it("numberList and rounding, copy and override", async () => { + let core = await createTestCore({ + doenetML: ` +

34.245023482352345 0.0023823402358234234

+

+

+

+

+ `, + }); + + let vals = [34.245023482352345, 0.0023823402358234234]; + + let text = ["34.25", "0.00238"].join(", "); + let textDig6 = ["34.245", "0.00238234"].join(", "); + let textDec6 = ["34.245023", "0.002382"].join(", "); + + await test_numberList({ + core, + name: `/nl`, + numbers: vals, + pName: `/p`, + text: text, + }); + await test_numberList({ + core, + name: `/nlDig6`, + numbers: vals, + pName: `/pDig6`, + text: textDig6, + }); + await test_numberList({ + core, + name: `/nlDig6a`, + numbers: vals, + pName: `/pDig6a`, + text: textDig6, + }); + await test_numberList({ + core, + name: `/nlDec6`, + numbers: vals, + pName: `/pDec6`, + text: textDec6, + }); + await test_numberList({ + core, + name: `/nlDec6a`, + numbers: vals, + pName: `/pDec6a`, + text: textDec6, + }); + }); + + it("numberList adapts to math and text", async () => { + let core = await createTestCore({ + doenetML: ` + 1 23 + +

Number list as math: $nl

+

Number list as text: $nl

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/m"].stateValues.value.tree).eqls([ + "list", + 1, + 2, + 3, + ]); + expect(stateVariables["/t"].stateValues.value).eq("1, 2, 3"); + }); + + it("numberList adapts to numberList", async () => { + let core = await createTestCore({ + doenetML: ` +

9 87

+ +

$nl

+

Change second number: $nl[2]

+ +

Change 1st and 3rd number via point: ($nl[1],$nl[3])

+ + `, + }); + + async function test_items(n1: number, n2: number, n3: number) { + let text = [n1, n2, n3].join(", "); + await test_numberList({ + core, + name: "/nl", + numbers: [n1, n2, n3], + pName: "/p1", + text, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/p2"].stateValues.text).eq(text); + } + + let n1 = 9, + n2 = 8, + n3 = 7; + + await test_items(n1, n2, n3); + + n2 = 83; + await updateMathInputValue({ + latex: n2.toString(), + componentName: "/mi1", + core, + }); + await test_items(n1, n2, n3); + + n1 = -1; + n3 = 2; + await updateMathInputValue({ + latex: `(${n1}, ${n3})`, + componentName: "/mi2", + core, + }); + await test_items(n1, n2, n3); + }); + + it("text and latex from numberList", async () => { + let core = await createTestCore({ + doenetML: ` + 7 -8 + +

Text: $nl.text

+

Latex: $nl.latex

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/pText"].stateValues.text).eq("Text: 7, -8"); + expect(stateVariables["/pLatex"].stateValues.text).eq("Latex: 7, -8"); + }); +}); diff --git a/packages/test-cypress/cypress/e2e/tagSpecific/mathlist.cy.js b/packages/test-cypress/cypress/e2e/tagSpecific/mathlist.cy.js deleted file mode 100644 index 70319f68d..000000000 --- a/packages/test-cypress/cypress/e2e/tagSpecific/mathlist.cy.js +++ /dev/null @@ -1,5197 +0,0 @@ -import { cesc, cesc2 } from "@doenet/utils"; - -describe("MathList Tag Tests", function () { - beforeEach(() => { - cy.clearIndexedDB(); - cy.visit("/"); - }); - - it("mathlist from string", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - a 1+1 - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let child1Name = - stateVariables["/_mathlist1"].activeChildren[0].componentName; - let child1Anchor = cesc2("#" + child1Name); - let child2Name = - stateVariables["/_mathlist1"].activeChildren[1].componentName; - let child2Anchor = cesc2("#" + child2Name); - - cy.log("Test value displayed in browser"); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1+1"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eqls(["+", 1, 1]); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eqls( - ["+", 1, 1], - ); - }); - }); - }); - - it("mathlist with error in string", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - a @ 1+1 - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let child1Name = - stateVariables["/_mathlist1"].activeChildren[0].componentName; - let child1Anchor = cesc2("#" + child1Name); - let child2Name = - stateVariables["/_mathlist1"].activeChildren[1].componentName; - let child2Anchor = cesc2("#" + child2Name); - let child3Name = - stateVariables["/_mathlist1"].activeChildren[2].componentName; - let child3Anchor = cesc2("#" + child3Name); - - cy.log("Test value displayed in browser"); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("_"); - }); - cy.get(child3Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1+1"); - }); - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq("_"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[2] - .componentName - ].stateValues.value, - ).eqls(["+", 1, 1]); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - "_", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eqls( - ["+", 1, 1], - ); - }); - }); - }); - - it("mathlist in attribute containing math and number macros", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

x - 3y - 7 - 11

-

-

$_point1.xs{assignNames="x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11"}

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/x1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/x2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("3y7"); - }); - cy.get(cesc("#\\/x3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("7"); - }); - cy.get(cesc("#\\/x4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("−4"); - }); - cy.get(cesc("#\\/x5")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("7"); - }); - cy.get(cesc("#\\/x6")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("−11"); - }); - cy.get(cesc("#\\/x7")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("7"); - }); - cy.get(cesc("#\\/x8")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("−"); - }); - cy.get(cesc("#\\/x9")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("11"); - }); - cy.get(cesc("#\\/x10")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("21xy"); - }); - cy.get(cesc("#\\/x11")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x+733y"); - }); - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let xs = stateVariables["/_point1"].stateValues.xs; - - expect(xs.length).eq(11); - expect(xs[0]).eq("x"); - expect(stateVariables["/x1"].stateValues.value).eq("x"); - expect(xs[1]).eqls(["/", ["*", 3, "y"], 7]); - expect(stateVariables["/x2"].stateValues.value).eqls([ - "/", - ["*", 3, "y"], - 7, - ]); - expect(xs[2]).eqls(7); - expect(stateVariables["/x3"].stateValues.value).eqls(7); - expect(xs[3]).eqls(-4); - expect(stateVariables["/x4"].stateValues.value).eqls(-4); - expect(xs[4]).eqls(7); - expect(stateVariables["/x5"].stateValues.value).eqls(7); - expect(xs[5]).eqls(-11); - expect(stateVariables["/x6"].stateValues.value).eqls(-11); - expect(xs[6]).eqls(7); - expect(stateVariables["/x7"].stateValues.value).eqls(7); - expect(xs[7]).eqls("-"); - expect(stateVariables["/x8"].stateValues.value).eqls("-"); - expect(xs[8]).eqls(11); - expect(stateVariables["/x9"].stateValues.value).eqls(11); - expect(xs[9]).eqls(["*", 21, "x", "y"]); - expect(stateVariables["/x10"].stateValues.value).eqls([ - "*", - 21, - "x", - "y", - ]); - expect(xs[10]).eqls(["/", ["+", "x", 7], ["*", 33, "y"]]); - expect(stateVariables["/x11"].stateValues.value).eqls([ - "/", - ["+", "x", 7], - ["*", 33, "y"], - ]); - }); - }); - - it("mathlist with math children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - 1+1 - - - - a1+1 - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1+1"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1+1"); - }); - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].activeChildren.length).eq( - 2, - ); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eqls(["+", 1, 1]); - expect( - stateVariables["/_mathlist1"].stateValues.maths.length, - ).eq(2); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eqls( - ["+", 1, 1], - ); - expect(stateVariables["/_mathlist2"].activeChildren.length).eq( - 2, - ); - expect( - stateVariables[ - stateVariables["/_mathlist2"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist2"].activeChildren[1] - .componentName - ].stateValues.value, - ).eqls(["+", 1, 1]); - expect( - stateVariables["/_mathlist2"].stateValues.maths.length, - ).eq(2); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eqls( - ["+", 1, 1], - ); - }); - }); - }); - - it("mathlist with math and string children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a q 1+1h - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let matha = stateVariables["/_mathlist1"].activeChildren[1]; - let mathaAnchor = cesc2("#" + matha.componentName); - let mathb = stateVariables["/_mathlist1"].activeChildren[3]; - let mathbAnchor = cesc2("#" + mathb.componentName); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(mathaAnchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("q"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1+1"); - }); - cy.get(mathbAnchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq("q"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[2] - .componentName - ].stateValues.value, - ).eqls(["+", 1, 1]); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[3] - .componentName - ].stateValues.value, - ).eq("h"); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - "q", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eqls( - ["+", 1, 1], - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - "h", - ); - }); - }); - }); - - it("mathlist with math and number children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - 1+1 - - - a1+1 - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_number1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("2"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_number2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("2"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].activeChildren.length).eq(2); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist1"].activeChildren[1] - .componentName - ].stateValues.number, - ).eq(2); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq(2); - expect(stateVariables["/_mathlist2"].activeChildren.length).eq(2); - expect( - stateVariables[ - stateVariables["/_mathlist2"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq("a"); - expect( - stateVariables[ - stateVariables["/_mathlist2"].activeChildren[1] - .componentName - ].stateValues.number, - ).eq(2); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq(2); - }); - }); - - it("mathlist with mathlist children, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - q r - h - - - b - u v - - i j - - - - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let child1Name = - stateVariables["/_mathlist2"].activeChildren[0].componentName; - let child1Anchor = cesc2("#" + child1Name); - let child2Name = - stateVariables["/_mathlist2"].activeChildren[1].componentName; - let child2Anchor = cesc2("#" + child2Name); - let child5Name = - stateVariables["/_mathlist5"].activeChildren[0].componentName; - let child5Anchor = cesc2("#" + child5Name); - let child6Name = - stateVariables["/_mathlist5"].activeChildren[1].componentName; - let child6Anchor = cesc2("#" + child6Name); - let child7Name = - stateVariables["/_mathlist6"].activeChildren[0].componentName; - let child7Anchor = cesc2("#" + child7Name); - let child8Name = - stateVariables["/_mathlist6"].activeChildren[1].componentName; - let child8Anchor = cesc2("#" + child8Name); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("q"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("r"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(child5Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(child6Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - cy.get(child7Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("i"); - }); - cy.get(child8Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("j"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - "q", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq( - "r", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - "h", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq( - "b", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq( - "u", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq( - "v", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[7]).eq( - "i", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[8]).eq( - "j", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - "q", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq( - "r", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[1]).eq( - "u", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[2]).eq( - "v", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[3]).eq( - "i", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[4]).eq( - "j", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[1]).eq( - "u", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[2]).eq( - "v", - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[0]).eq( - "u", - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[1]).eq( - "v", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[0]).eq( - "i", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[1]).eq( - "j", - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_mathinput1") + " textarea").type( - "{end}{backspace}1{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput2") + " textarea").type( - "{end}{backspace}2{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput3") + " textarea").type( - "{end}{backspace}3{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput4") + " textarea").type( - "{end}{backspace}4{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput5") + " textarea").type( - "{end}{backspace}5{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput6") + " textarea").type( - "{end}{backspace}6{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput7") + " textarea").type( - "{end}{backspace}7{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput8") + " textarea").type( - "{end}{backspace}8{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput9") + " textarea").type( - "{end}{backspace}9{enter}", - { force: true }, - ); - - cy.get(child8Anchor).should("contain.text", "9"); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1"); - }); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("2"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("3"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("4"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("5"); - }); - cy.get(child5Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("6"); - }); - cy.get(child6Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("7"); - }); - cy.get(child7Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("8"); - }); - cy.get(child8Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("9"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - 1, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - 2, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq( - 3, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - 4, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq( - 5, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq( - 6, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq( - 7, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[7]).eq( - 8, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[8]).eq( - 9, - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - 2, - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq( - 3, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[0]).eq( - 5, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[1]).eq( - 6, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[2]).eq( - 7, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[3]).eq( - 8, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[4]).eq( - 9, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[0]).eq( - 5, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[1]).eq( - 6, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[2]).eq( - 7, - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[0]).eq( - 6, - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[1]).eq( - 7, - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[0]).eq( - 8, - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[1]).eq( - 9, - ); - }); - }); - }); - - it("mathlist with mathlist children and sugar, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - q r - h - - - b - u v - - i j - - - - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let child0Name = - stateVariables["/_mathlist1"].activeChildren[0].componentName; - let child0Anchor = cesc2("#" + child0Name); - let child1Name = - stateVariables["/_mathlist2"].activeChildren[0].componentName; - let child1Anchor = cesc2("#" + child1Name); - let child2Name = - stateVariables["/_mathlist2"].activeChildren[1].componentName; - let child2Anchor = cesc2("#" + child2Name); - let child4Name = - stateVariables["/_mathlist4"].activeChildren[0].componentName; - let child4Anchor = cesc2("#" + child4Name); - let child5Name = - stateVariables["/_mathlist5"].activeChildren[0].componentName; - let child5Anchor = cesc2("#" + child5Name); - let child6Name = - stateVariables["/_mathlist5"].activeChildren[1].componentName; - let child6Anchor = cesc2("#" + child6Name); - let child7Name = - stateVariables["/_mathlist6"].activeChildren[0].componentName; - let child7Anchor = cesc2("#" + child7Name); - let child8Name = - stateVariables["/_mathlist6"].activeChildren[1].componentName; - let child8Anchor = cesc2("#" + child8Name); - - cy.log("Test value displayed in browser"); - cy.get(child0Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("q"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("r"); - }); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(child4Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(child5Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(child6Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - cy.get(child7Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("i"); - }); - cy.get(child8Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("j"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - "q", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq( - "r", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - "h", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq( - "b", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq( - "u", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq( - "v", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[7]).eq( - "i", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[8]).eq( - "j", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - "q", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq( - "r", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[1]).eq( - "u", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[2]).eq( - "v", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[3]).eq( - "i", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[4]).eq( - "j", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[1]).eq( - "u", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[2]).eq( - "v", - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[0]).eq( - "u", - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[1]).eq( - "v", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[0]).eq( - "i", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[1]).eq( - "j", - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_mathinput1") + " textarea").type( - "{end}{backspace}1{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput2") + " textarea").type( - "{end}{backspace}2{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput3") + " textarea").type( - "{end}{backspace}3{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput4") + " textarea").type( - "{end}{backspace}4{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput5") + " textarea").type( - "{end}{backspace}5{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput6") + " textarea").type( - "{end}{backspace}6{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput7") + " textarea").type( - "{end}{backspace}7{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput8") + " textarea").type( - "{end}{backspace}8{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput9") + " textarea").type( - "{end}{backspace}9{enter}", - { force: true }, - ); - - cy.get(child8Anchor).should("contain.text", "9"); - - cy.log("Test value displayed in browser"); - cy.get(child0Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1"); - }); - cy.get(child1Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("2"); - }); - cy.get(child2Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("3"); - }); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("4"); - }); - cy.get(child4Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("5"); - }); - cy.get(child5Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("6"); - }); - cy.get(child6Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("7"); - }); - cy.get(child7Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("8"); - }); - cy.get(child8Anchor) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("9"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - 1, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - 2, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq( - 3, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - 4, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq( - 5, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq( - 6, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq( - 7, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[7]).eq( - 8, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[8]).eq( - 9, - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - 2, - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq( - 3, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[0]).eq( - 5, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[1]).eq( - 6, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[2]).eq( - 7, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[3]).eq( - 8, - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[4]).eq( - 9, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[0]).eq( - 5, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[1]).eq( - 6, - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[2]).eq( - 7, - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[0]).eq( - 6, - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[1]).eq( - 7, - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[0]).eq( - 8, - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[1]).eq( - 9, - ); - }); - }); - }); - - it("mathlist with self references", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - q r - - - - - u v - - - - - - - $mid{name="mid2"} - - - - - - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - let child0Name = "/_math1"; - let ca0 = cesc2("#" + child0Name); - let child1Name = - stateVariables["/_mathlist2"].activeChildren[0].componentName; - let ca1 = cesc2("#" + child1Name); - let child2Name = - stateVariables["/_mathlist2"].activeChildren[1].componentName; - let ca2 = cesc2("#" + child2Name); - let child3Name = "/m4"; - let ca3 = cesc2("#" + child3Name); - let child4Name = "/m2"; - let ca4 = cesc2("#" + child4Name); - let child5Name = - stateVariables["/_mathlist5"].activeChildren[0].componentName; - let ca5 = cesc2("#" + child5Name); - let child6Name = - stateVariables["/_mathlist5"].activeChildren[1].componentName; - let ca6 = cesc2("#" + child6Name); - let child7Name = "/m8"; - let ca7 = cesc2("#" + child7Name); - let child8Name = "/m9"; - let ca8 = cesc2("#" + child8Name); - let child9Name = - stateVariables["/mid2"].activeChildren[0].componentName; - let ca9 = cesc2("#" + child9Name); - let child10Name = - stateVariables[ - stateVariables["/mid2"].activeChildren[1].componentName - ].activeChildren[0].componentName; - let ca10 = cesc2("#" + child10Name); - let child11Name = - stateVariables[ - stateVariables["/mid2"].activeChildren[1].componentName - ].activeChildren[1].componentName; - let ca11 = cesc2("#" + child11Name); - - let childAnchors = [ - ca0, - ca1, - ca2, - ca3, - ca4, - ca5, - ca6, - ca7, - ca8, - ca9, - ca10, - ca11, - ]; - let vals = ["a", "q", "r", "u", "v"]; - let mapping = [0, 1, 2, 2, 0, 3, 4, 1, 0, 0, 3, 4]; - let mv = (i) => vals[mapping[i]]; - - let maths = stateVariables["/_mathlist1"].stateValues.maths; - - let mathinputAnchors = []; - for (let i in mapping) { - mathinputAnchors.push( - cesc(`#\\/_mathinput${Number(i) + 1}`) + ` textarea`, - ); - } - - cy.log("Test value displayed in browser"); - - for (let i in mapping) { - cy.get(childAnchors[i]) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal(mv(i)); - }); - } - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - for (let i in mapping) { - expect(maths[i]).eq(mv(i)); - } - }); - - cy.log("change values"); - - for (let changeInd in mapping) { - cy.window().then(async (win) => { - vals[mapping[changeInd]] = Number(changeInd); - cy.get(mathinputAnchors[changeInd]).type( - "{end}{backspace}" + changeInd + "{enter}", - { force: true }, - ); - cy.get(childAnchors[changeInd]).should( - "contain.text", - String(mv(changeInd)), - ); - - cy.log("Test value displayed in browser"); - - for (let i in mapping) { - cy.get(childAnchors[i]) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal(String(mv(i))); - }); - } - - cy.log( - "Test internal values are set to the correct values", - ); - cy.window().then(async (win) => { - let stateVariables = - await win.returnAllStateVariables1(); - let maths = - stateVariables["/_mathlist1"].stateValues.maths; - - for (let i in mapping) { - expect(maths[i]).eq(mv(i)); - } - }); - }); - } - }); - }); - - // TODO: address maximum number in rendered children of mathlist - it("mathlist with maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - q r l k - h - - - b - u v - - i j k - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - // let stateVariables = await win.returnAllStateVariables1(); - // let child1Name = stateVariables['/_mathlist2'].activeChildren[0].componentName; - // let child1Anchor = cesc2('#' + child1Name); - // let child2Name = stateVariables['/_mathlist2'].activeChildren[1].componentName; - // let child2Anchor = cesc2('#' + child2Name); - // let child5Name = stateVariables['/_mathlist5'].activeChildren[0].componentName; - // let child5Anchor = cesc2('#' + child5Name); - // let child6Name = stateVariables['/_mathlist6'].activeChildren[0].componentName; - // let child6Anchor = cesc2('#' + child6Name); - - // cy.log('Test value displayed in browser') - // cy.get(cesc('#\\/_math1')).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('a') - // }) - // cy.get(child1Anchor).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('q') - // }) - // cy.get(child2Anchor).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('r') - // }) - // cy.get(cesc('#\\/_math2')).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('h') - // }) - // cy.get(cesc('#\\/_math3')).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('b') - // }) - // cy.get(child5Anchor).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('u') - // }) - // cy.get(child6Anchor).find('.mjx-mrow').eq(0).invoke('text').then((text) => { - // expect(text.trim()).equal('i') - // }) - - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("q"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("r"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(5) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(6) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("i"); - }); - cy.get(cesc("#\\/_document1")) - .find(".mjx-mrow") - .eq(7) - .should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables["/_mathlist1"].stateValues.maths.length, - ).eq(7); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq( - "a", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq( - "q", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq( - "r", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq( - "h", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq( - "b", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq( - "u", - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq( - "i", - ); - expect( - stateVariables["/_mathlist2"].stateValues.maths.length, - ).eq(2); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq( - "q", - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq( - "r", - ); - expect( - stateVariables["/_mathlist3"].stateValues.maths.length, - ).eq(4); - expect(stateVariables["/_mathlist3"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[1]).eq( - "u", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[2]).eq( - "i", - ); - expect(stateVariables["/_mathlist3"].stateValues.maths[3]).eq( - "j", - ); - expect( - stateVariables["/_mathlist4"].stateValues.maths.length, - ).eq(2); - expect(stateVariables["/_mathlist4"].stateValues.maths[0]).eq( - "b", - ); - expect(stateVariables["/_mathlist4"].stateValues.maths[1]).eq( - "u", - ); - expect( - stateVariables["/_mathlist5"].stateValues.maths.length, - ).eq(2); - expect(stateVariables["/_mathlist5"].stateValues.maths[0]).eq( - "u", - ); - expect(stateVariables["/_mathlist5"].stateValues.maths[1]).eq( - "v", - ); - expect( - stateVariables["/_mathlist6"].stateValues.maths.length, - ).eq(3); - expect(stateVariables["/_mathlist6"].stateValues.maths[0]).eq( - "i", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[1]).eq( - "j", - ); - expect(stateVariables["/_mathlist6"].stateValues.maths[2]).eq( - "k", - ); - }); - }); - }); - - it("copy mathlist and overwrite maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

a b c d e

-

$ml1{maxNumber="3" name="ml2"}

-

$ml2{maxNumber="" name="ml3"}

- -

a b c d e

-

$ml4{maxNumber="4" name="ml5"}

-

$ml5{maxNumber="" name="ml6"}

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("d"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e"); - }); - cy.get(cesc("#\\/_p1")).find(".mjx-mrow").eq(5).should("not.exist"); - - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p2")).find(".mjx-mrow").eq(3).should("not.exist"); - - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("d"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e"); - }); - cy.get(cesc("#\\/_p3")).find(".mjx-mrow").eq(5).should("not.exist"); - - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p4")).find(".mjx-mrow").eq(3).should("not.exist"); - - cy.get(cesc("#\\/_p5")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p5")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p5")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p5")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("d"); - }); - cy.get(cesc("#\\/_p5")).find(".mjx-mrow").eq(4).should("not.exist"); - - cy.get(cesc("#\\/_p6")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p6")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p6")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p6")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("d"); - }); - cy.get(cesc("#\\/_p6")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e"); - }); - cy.get(cesc("#\\/_p6")).find(".mjx-mrow").eq(5).should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "a", - "b", - "c", - ]); - expect(stateVariables["/ml3"].stateValues.maths).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/ml4"].stateValues.maths).eqls([ - "a", - "b", - "c", - ]); - expect(stateVariables["/ml5"].stateValues.maths).eqls([ - "a", - "b", - "c", - "d", - ]); - expect(stateVariables["/ml6"].stateValues.maths).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - }); - }); - }); - - it("dynamic maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

x y z u v

-

$ml1{maxNumber="$mn2" name="ml2"}

-

Maximum number 1:

-

Maximum number 2:

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .should("contain.text", "u"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(4) - .should("contain.text", "v"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls(["x", "y"]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "x", - "y", - "z", - "u", - "v", - ]); - }); - - cy.log("clear first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea") - .type("{end}{backspace}", { force: true }) - .blur(); - - cy.get(cesc("#\\/_p1")).should("contain.text", "v"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .should("contain.text", "u"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(4) - .should("contain.text", "v"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .should("contain.text", "u"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(4) - .should("contain.text", "v"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls([ - "x", - "y", - "z", - "u", - "v", - ]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "x", - "y", - "z", - "u", - "v", - ]); - }); - - cy.log("number in second maxnum"); - cy.get(cesc("#\\/mn2") + " textarea").type("3{enter}", { force: true }); - - cy.get(cesc("#\\/_p2")).should("not.contain.text", "v"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .should("contain.text", "u"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(4) - .should("contain.text", "v"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("v"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls([ - "x", - "y", - "z", - "u", - "v", - ]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "x", - "y", - "z", - ]); - }); - - cy.log("number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("4{enter}", { force: true }); - - cy.get(cesc("#\\/_p1")).should("not.contain.text", "v"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(4) - .should("not.exist"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .should("contain.text", "u"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("u"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls([ - "x", - "y", - "z", - "u", - ]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "x", - "y", - "z", - ]); - }); - - cy.log("change number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - - cy.get(cesc("#\\/_p1")).should("not.contain.text", "y"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .should("contain.text", "x"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .should("contain.text", "y"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .should("contain.text", "z"); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - - cy.get(cesc("#\\/_p1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("y"); - }); - cy.get(cesc("#\\/_p2") + " .mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("z"); - }); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml1"].stateValues.maths).eqls(["x"]); - expect(stateVariables["/ml2"].stateValues.maths).eqls([ - "x", - "y", - "z", - ]); - }); - }); - - it("mathlist with merge math lists", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a - b,c,d - e,f - g - -

Merge math lists:

- -

Third math: $_mathlist1.math3

-

Fifth math: $_mathlist1.math5

- -

Change values: - - - - - - - -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b,c,d"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e,f"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("g"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e,f"); - }); - cy.get(cesc("#\\/_p3")).find(".mjx-mrow").should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 4, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eqls([ - "list", - "b", - "c", - "d", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eqls([ - "list", - "e", - "f", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("g"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eqls([ - "list", - "e", - "f", - ]); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq( - undefined, - ); - }); - - cy.log("change values"); - cy.get(cesc("#\\/mi1") + " textarea").type("{end}{backspace}h{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi2") + " textarea").type("{end}{backspace}i{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi3") + " textarea").type("{end}{backspace}j{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi4") + " textarea").type("{end}{backspace}k{enter}", { - force: true, - }); - - cy.log("Test value displayed in browser"); - - cy.get(cesc("#\\/_math4") + " .mjx-mrow").should("contain.text", "k"); - - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b,c,i"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e,j"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("k"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e,j"); - }); - cy.get(cesc("#\\/_p3")).find(".mjx-mrow").should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 4, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("h"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eqls([ - "list", - "b", - "c", - "i", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eqls([ - "list", - "e", - "j", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("k"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eqls([ - "list", - "e", - "j", - ]); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq( - undefined, - ); - }); - - cy.log("merge math lists"); - cy.get(cesc("#\\/_booleaninput1")).click(); - - cy.get(cesc("#\\/_p2") + " .mjx-mrow").should("contain.text", "c"); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b,c,i"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e,j"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("k"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 7, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("h"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq("b"); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq("c"); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("i"); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq("e"); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq("j"); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq("k"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eq("c"); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq("e"); - }); - - cy.log("change values"); - cy.get(cesc("#\\/mi1") + " textarea").type("{end}{backspace}l{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi2") + " textarea").type("{end}{backspace}m{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi3") + " textarea").type("{end}{backspace}n{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi4") + " textarea").type("{end}{backspace}o{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi5") + " textarea").type("{end}{backspace}p{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi6") + " textarea").type("{end}{backspace}q{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi7") + " textarea").type("{end}{backspace}r{enter}", { - force: true, - }); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math4") + " .mjx-mrow").should("contain.text", "r"); - - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("l"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("m,n,o"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("p,q"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("r"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("n"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("p"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 7, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("l"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq("m"); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq("n"); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("o"); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq("p"); - expect(stateVariables["/_mathlist1"].stateValues.maths[5]).eq("q"); - expect(stateVariables["/_mathlist1"].stateValues.maths[6]).eq("r"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eq("n"); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq("p"); - }); - - cy.log("stop merging again"); - cy.get(cesc("#\\/_booleaninput1")).click(); - cy.get(cesc("#\\/_p2") + " .mjx-mrow").should("contain.text", "p,q"); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("l"); - }); - cy.get(cesc("#\\/_math2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("m,n,o"); - }); - cy.get(cesc("#\\/_math3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("p,q"); - }); - cy.get(cesc("#\\/_math4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("r"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("p,q"); - }); - cy.get(cesc("#\\/_p3")).find(".mjx-mrow").should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 4, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("l"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eqls([ - "list", - "m", - "n", - "o", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eqls([ - "list", - "p", - "q", - ]); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("r"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eqls([ - "list", - "p", - "q", - ]); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq( - undefined, - ); - }); - }); - - it("always merge math lists when have one math child", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - a,b,c,d,e - - -

Third math: $_mathlist1.math3

-

Fifth math: $_mathlist1.math5

- -

Change values: - - - - - -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a,b,c,d,e"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("e"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 5, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq("b"); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq("c"); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("d"); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq("e"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eq("c"); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq("e"); - }); - - cy.log("change values"); - cy.get(cesc("#\\/mi1") + " textarea").type("{end}{backspace}f{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi2") + " textarea").type("{end}{backspace}g{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi3") + " textarea").type("{end}{backspace}h{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi4") + " textarea").type("{end}{backspace}i{enter}", { - force: true, - }); - cy.get(cesc("#\\/mi5") + " textarea").type("{end}{backspace}j{enter}", { - force: true, - }); - - cy.log("Test value displayed in browser"); - - cy.get(cesc("#\\/_p2") + " .mjx-mrow").should("contain.text", "j"); - cy.get(cesc("#\\/_math1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("f,g,h,i,j"); - }); - cy.get(cesc("#\\/_p1")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("h"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("j"); - }); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 5, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("f"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq("g"); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq("h"); - expect(stateVariables["/_mathlist1"].stateValues.maths[3]).eq("i"); - expect(stateVariables["/_mathlist1"].stateValues.maths[4]).eq("j"); - expect(stateVariables["/_mathlist1"].stateValues.math3).eq("h"); - expect(stateVariables["/_mathlist1"].stateValues.math5).eq("j"); - }); - }); - - it("maxNumber with when have one math child", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - - a,b,c,d,e - - -

All maths from list: $ml.maths

- -

Copied math: $m

- -

Copied mathlist: $ml

- - `, - }, - "*", - ); - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "a,b,c"); - - cy.get(cesc2("#/m") + " .mjx-mrow") - .eq(0) - .should("have.text", "a,b,c"); - - cy.get(cesc2("#/_p1") + " .mjx-mrow") - .eq(0) - .should("have.text", "a"); - cy.get(cesc2("#/_p1") + " .mjx-mrow") - .eq(1) - .should("have.text", "b"); - cy.get(cesc2("#/_p1") + " .mjx-mrow") - .eq(2) - .should("have.text", "c"); - cy.get(cesc2("#/_p1") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - - cy.get(cesc2("#/_p2") + " .mjx-mrow") - .eq(0) - .should("have.text", "a,b,c,d,e"); - - cy.get(cesc2("#/_p3") + " .mjx-mrow") - .eq(0) - .should("have.text", "a,b,c"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml"].stateValues.maths).eqls([ - "a", - "b", - "c", - ]); - }); - }); - - it("maxNumber with merge math lists", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - - - 1 - 2, 345 6 - 7,8,9 - 10 11 - 12, 13, 14, 15 - 16 17 18 19 20 - -

Merge math lists:

-

Maximum number:

- -

All maths from list: $ml.maths

- -

Copied mathlist: $ml

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - - for (let i = 0; i < 3; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - - cy.get(cesc2("#/maxnum") + " textarea").type( - "{end}{backspace}6{enter}", - { - force: true, - }, - ); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - - for (let i = 0; i < 6; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(6) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(5) - .should("not.exist"); - - cy.get(cesc2("#/maxnum") + " textarea").type( - "{end}{backspace}7{enter}", - { - force: true, - }, - ); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("contain.text", "7,8,9"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(6) - .should("not.exist"); - - for (let i = 0; i < 6; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(6) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(7) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(6) - .should("not.exist"); - - cy.get(cesc2("#/mml")).click(); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("not.contain.text", "9"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(5) - .should("have.text", "7"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(6) - .should("not.exist"); - - for (let i = 0; i < 7; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(7) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(5) - .should("have.text", "7"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(6) - .should("not.exist"); - - cy.get(cesc2("#/maxnum") + " textarea").type( - "{end}{backspace}13{enter}", - { - force: true, - }, - ); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("contain.text", "13"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(6) - .should("have.text", "10"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(7) - .should("have.text", "11"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(8) - .should("have.text", "12,13"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(9) - .should("not.exist"); - - for (let i = 0; i < 13; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(13) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(6) - .should("have.text", "10"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(7) - .should("have.text", "11"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(8) - .should("have.text", "12,13"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(9) - .should("not.exist"); - - cy.get(cesc2("#/mml")).click(); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("contain.text", "18"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(6) - .should("have.text", "10"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(7) - .should("have.text", "11"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(8) - .should("have.text", "12,13,14,15"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(9) - .should("have.text", "16"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(10) - .should("have.text", "17"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(11) - .should("have.text", "18"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(12) - .should("not.exist"); - - for (let i = 0; i < 6; i++) { - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(6) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(7) - .should("have.text", "10"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(8) - .should("have.text", "11"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(9) - .should("have.text", "12,13,14,15"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(10) - .should("have.text", "16"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(11) - .should("have.text", "17"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(12) - .should("have.text", "18"); - cy.get(cesc2("#/pmaths") + " .mjx-mrow") - .eq(13) - .should("not.exist"); - - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(1) - .should("have.text", "2,3"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(2) - .should("have.text", "4"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(3) - .should("have.text", "5"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(4) - .should("have.text", "6"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(5) - .should("have.text", "7,8,9"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(6) - .should("have.text", "10"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(7) - .should("have.text", "11"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(8) - .should("have.text", "12,13,14,15"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(9) - .should("have.text", "16"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(10) - .should("have.text", "17"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(11) - .should("have.text", "18"); - cy.get(cesc2("#/pcopy") + " .mjx-mrow") - .eq(12) - .should("not.exist"); - }); - - it("maxNumber with mathlist or numberlist child", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - - - -

1 2 3

-

1 2 3

-

1 2 3

- -

$ml.maths

-

$mlml.maths

-

$mlnl.maths

- -

$ml

-

$mlml

-

$mlnl

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1,2"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - for (let i = 0; i < 2; i++) { - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1,2"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml"].stateValues.maths).eqls([1, 2]); - expect(stateVariables["/ml"].stateValues.latex).eqls("1, 2"); - expect(stateVariables["/ml"].stateValues.text).eqls("1, 2"); - expect(stateVariables["/mlml"].stateValues.maths).eqls([1, 2]); - expect(stateVariables["/mlml"].stateValues.latex).eqls("1, 2"); - expect(stateVariables["/mlml"].stateValues.text).eqls("1, 2"); - expect(stateVariables["/mlnl"].stateValues.maths).eqls([1, 2]); - expect(stateVariables["/mlnl"].stateValues.latex).eqls("1, 2"); - expect(stateVariables["/mlnl"].stateValues.text).eqls("1, 2"); - }); - - cy.get(cesc2("#/maxn") + " textarea").type("{end}{backspace}4{enter}", { - force: true, - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("contain.text", "3"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "3"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(2) - .should("have.text", "3"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1,2,3"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - for (let i = 0; i < 3; i++) { - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(2) - .should("have.text", "3"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(1) - .should("have.text", "2"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(2) - .should("have.text", "3"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(3) - .should("not.exist"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1,2,3"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(2) - .should("not.exist"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml"].stateValues.maths).eqls([1, 2, 3]); - expect(stateVariables["/ml"].stateValues.latex).eqls("1, 2, 3"); - expect(stateVariables["/ml"].stateValues.text).eqls("1, 2, 3"); - expect(stateVariables["/mlml"].stateValues.maths).eqls([1, 2, 3]); - expect(stateVariables["/mlml"].stateValues.latex).eqls("1, 2, 3"); - expect(stateVariables["/mlml"].stateValues.text).eqls("1, 2, 3"); - expect(stateVariables["/mlnl"].stateValues.maths).eqls([1, 2, 3]); - expect(stateVariables["/mlnl"].stateValues.latex).eqls("1, 2, 3"); - expect(stateVariables["/mlnl"].stateValues.text).eqls("1, 2, 3"); - }); - - cy.get(cesc2("#/maxn") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow").should("not.contain.text", "2"); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/mlml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/mlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - for (let i = 0; i < 1; i++) { - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(i) - .should("have.text", `${i + 1}`); - } - cy.get(cesc2("#/pmathsml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/pmathsmlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopyml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopymlml") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(0) - .should("have.text", "1"); - cy.get(cesc2("#/pcopymlnl") + " .mjx-mrow") - .eq(1) - .should("not.exist"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/ml"].stateValues.maths).eqls([1]); - expect(stateVariables["/ml"].stateValues.latex).eqls("1"); - expect(stateVariables["/ml"].stateValues.text).eqls("1"); - expect(stateVariables["/mlml"].stateValues.maths).eqls([1]); - expect(stateVariables["/mlml"].stateValues.latex).eqls("1"); - expect(stateVariables["/mlml"].stateValues.text).eqls("1"); - expect(stateVariables["/mlnl"].stateValues.maths).eqls([1]); - expect(stateVariables["/mlnl"].stateValues.latex).eqls("1"); - expect(stateVariables["/mlnl"].stateValues.text).eqls("1"); - }); - }); - - // TODO: deal with hidden children of a mathlist - it("mathlist within mathlists, with child hide", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

a b c

- -

$_mathlist1{name="mathlist1a" hide="false"}

- -

- x - $_mathlist1{hide="false"} - y - $mathlist1a -

- -

$_mathlist2{name="mathlist3" maxNumber="6"}

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - - cy.get(cesc("#\\/_p1")).should("have.text", ""); - - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p2")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p2")).find(".mjx-mrow").eq(3).should("not.exist"); - - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(5) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p3")) - .find(".mjx-mrow") - .eq(6) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p3")).find(".mjx-mrow").eq(7).should("not.exist"); - - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("x"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(1) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(2) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(3) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_p4")) - .find(".mjx-mrow") - .eq(4) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_p4")).find(".mjx-mrow").eq(5).should("not.exist"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_mathlist1"].stateValues.maths.length).eq( - 3, - ); - expect(stateVariables["/_mathlist1"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/_mathlist1"].stateValues.maths[1]).eq("b"); - expect(stateVariables["/_mathlist1"].stateValues.maths[2]).eq("c"); - expect(stateVariables["/mathlist1a"].stateValues.maths.length).eq( - 3, - ); - expect(stateVariables["/mathlist1a"].stateValues.maths[0]).eq("a"); - expect(stateVariables["/mathlist1a"].stateValues.maths[1]).eq("b"); - expect(stateVariables["/mathlist1a"].stateValues.maths[2]).eq("c"); - expect(stateVariables["/_mathlist2"].stateValues.maths.length).eq( - 8, - ); - expect(stateVariables["/_mathlist2"].stateValues.maths[0]).eq("x"); - expect(stateVariables["/_mathlist2"].stateValues.maths[1]).eq("a"); - expect(stateVariables["/_mathlist2"].stateValues.maths[2]).eq("b"); - expect(stateVariables["/_mathlist2"].stateValues.maths[3]).eq("c"); - expect(stateVariables["/_mathlist2"].stateValues.maths[4]).eq("y"); - expect(stateVariables["/_mathlist2"].stateValues.maths[5]).eq("a"); - expect(stateVariables["/_mathlist2"].stateValues.maths[6]).eq("b"); - expect(stateVariables["/_mathlist2"].stateValues.maths[7]).eq("c"); - expect(stateVariables["/mathlist3"].stateValues.maths.length).eq(6); - expect(stateVariables["/mathlist3"].stateValues.maths[0]).eq("x"); - expect(stateVariables["/mathlist3"].stateValues.maths[1]).eq("a"); - expect(stateVariables["/mathlist3"].stateValues.maths[2]).eq("b"); - expect(stateVariables["/mathlist3"].stateValues.maths[3]).eq("c"); - expect(stateVariables["/mathlist3"].stateValues.maths[4]).eq("y"); - expect(stateVariables["/mathlist3"].stateValues.maths[5]).eq("a"); - }); - }); - - it("mathlist does not force composite replacement, even in boolean", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - $nothing = - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_boolean1")).should("have.text", "true"); - }); - - it("functionSymbols", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` -

f(x) h(x) a(x)

-

f(x) h(x) a(x)

-

- h(x) g(x) a(x) - -

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(0) - .should("have.text", "f(x)"); - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(2) - .should("have.text", "hx"); - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(3) - .should("have.text", "ax"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(0) - .should("have.text", "fx"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(1) - .should("have.text", "h(x)"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(3) - .should("have.text", "ax"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(0) - .should("have.text", "hx"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(1) - .should("have.text", "g(x)"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(3) - .should("have.text", "a(x)"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/mldef"].stateValues.maths).eqls([ - ["apply", "f", "x"], - ["*", "h", "x"], - ["*", "a", "x"], - ]); - expect(stateVariables["/mlh"].stateValues.maths).eqls([ - ["*", "f", "x"], - ["apply", "h", "x"], - ["*", "a", "x"], - ]); - expect(stateVariables["/mlmixed"].stateValues.maths).eqls([ - ["*", "h", "x"], - ["apply", "g", "x"], - ["apply", "a", "x"], - ]); - }); - }); - - it("sourcesAreFunctionSymbols", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - - f - g - h - a - -

$fun1(x) $fun3(x) $fun4(x)

-

$fun1(x) $fun3(x) $fun4(x)

-

- $fun3(x) $fun2(x) $fun4(x) - -

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(0) - .should("have.text", "fx"); - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(1) - .should("have.text", "hx"); - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(2) - .should("have.text", "ax"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(0) - .should("have.text", "fx"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(1) - .should("have.text", "h(x)"); - cy.get(cesc2("#/mlh") + " .mjx-mrow") - .eq(3) - .should("have.text", "ax"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(0) - .should("have.text", "hx"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(1) - .should("have.text", "g(x)"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(3) - .should("have.text", "a(x)"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/mldef"].stateValues.maths).eqls([ - ["*", "f", "x"], - ["*", "h", "x"], - ["*", "a", "x"], - ]); - expect(stateVariables["/mlh"].stateValues.maths).eqls([ - ["*", "f", "x"], - ["apply", "h", "x"], - ["*", "a", "x"], - ]); - expect(stateVariables["/mlmixed"].stateValues.maths).eqls([ - ["*", "h", "x"], - ["apply", "g", "x"], - ["apply", "a", "x"], - ]); - }); - }); - - it("splitSymbols", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` -

xy yz

-

xy yz

-

- xy yz zx - -

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(0) - .should("have.text", "xy"); - cy.get(cesc2("#/mldef") + " .mjx-mrow") - .eq(1) - .should("have.text", "yz"); - cy.get(cesc2("#/mln") + " .mjx-mrow") - .eq(0) - .should("have.text", "xy"); - cy.get(cesc2("#/mln") + " .mjx-mrow") - .eq(1) - .should("have.text", "yz"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(0) - .should("have.text", "xy"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(1) - .should("have.text", "yz"); - cy.get(cesc2("#/mlmixed") + " .mjx-mrow") - .eq(2) - .should("have.text", "zx"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/mldef"].stateValues.maths).eqls([ - ["*", "x", "y"], - ["*", "y", "z"], - ]); - expect(stateVariables["/mln"].stateValues.maths).eqls(["xy", "yz"]); - expect(stateVariables["/mlmixed"].stateValues.maths).eqls([ - "xy", - ["*", "y", "z"], - "zx", - ]); - }); - }); - - it("mathlist and rounding, from strings", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

- -

-

-

-

-

-

- -

$_mathlist1.maths{assignNames="m1_1 m1_2 m1_3 m1_4 m1_5"}

-

$_mathlist2.maths{assignNames="m2_1 m2_2 m2_3 m2_4 m2_5"}

-

$_mathlist3.maths{assignNames="m3_1 m3_2 m3_3 m3_4 m3_5"}

-

$_mathlist4.maths{assignNames="m4_1 m4_2 m4_3 m4_4 m4_5"}

-

$_mathlist5.maths{assignNames="m5_1 m5_2 m5_3 m5_4 m5_5"}

-

$_mathlist6.maths{assignNames="m6_1 m6_2 m6_3 m6_4 m6_5"}

- -

-

-

-

-

-

- -

$_mathlist1.maths{assignNames="m1_1a m1_2a m1_3a m1_4a m1_5a" link="false"}

-

$_mathlist2.maths{assignNames="m2_1a m2_2a m2_3a m2_4a m2_5a" link="false"}

-

$_mathlist3.maths{assignNames="m3_1a m3_2a m3_3a m3_4a m3_5a" link="false"}

-

$_mathlist4.maths{assignNames="m4_1a m4_2a m4_3a m4_4a m4_5a" link="false"}

-

$_mathlist5.maths{assignNames="m5_1a m5_2a m5_3a m5_4a m5_5a" link="false"}

-

$_mathlist6.maths{assignNames="m6_1a m6_2a m6_3a m6_4a m6_5a" link="false"}

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - for (let i = 0; i < 5; i++) { - let maths1, maths2, maths3, maths4, maths5, maths6; - - if (i === 0) { - maths1 = stateVariables["/_mathlist1"].activeChildren.map( - (x) => x.componentName, - ); - maths2 = stateVariables["/_mathlist2"].activeChildren.map( - (x) => x.componentName, - ); - maths3 = stateVariables["/_mathlist3"].activeChildren.map( - (x) => x.componentName, - ); - maths4 = stateVariables["/_mathlist4"].activeChildren.map( - (x) => x.componentName, - ); - maths5 = stateVariables["/_mathlist5"].activeChildren.map( - (x) => x.componentName, - ); - maths6 = stateVariables["/_mathlist6"].activeChildren.map( - (x) => x.componentName, - ); - } else if (i === 1) { - maths1 = stateVariables["/ml1a"].activeChildren.map( - (x) => x.componentName, - ); - maths2 = stateVariables["/ml2a"].activeChildren.map( - (x) => x.componentName, - ); - maths3 = stateVariables["/ml3a"].activeChildren.map( - (x) => x.componentName, - ); - maths4 = stateVariables["/ml4a"].activeChildren.map( - (x) => x.componentName, - ); - maths5 = stateVariables["/ml5a"].activeChildren.map( - (x) => x.componentName, - ); - maths6 = stateVariables["/ml6a"].activeChildren.map( - (x) => x.componentName, - ); - } else if (i === 2) { - maths1 = stateVariables["/pms1"].activeChildren.map( - (x) => x.componentName, - ); - maths2 = stateVariables["/pms2"].activeChildren.map( - (x) => x.componentName, - ); - maths3 = stateVariables["/pms3"].activeChildren.map( - (x) => x.componentName, - ); - maths4 = stateVariables["/pms4"].activeChildren.map( - (x) => x.componentName, - ); - maths5 = stateVariables["/pms5"].activeChildren.map( - (x) => x.componentName, - ); - maths6 = stateVariables["/pms6"].activeChildren.map( - (x) => x.componentName, - ); - } else if (i === 3) { - maths1 = stateVariables["/ml1b"].activeChildren.map( - (x) => x.componentName, - ); - maths2 = stateVariables["/ml2b"].activeChildren.map( - (x) => x.componentName, - ); - maths3 = stateVariables["/ml3b"].activeChildren.map( - (x) => x.componentName, - ); - maths4 = stateVariables["/ml4b"].activeChildren.map( - (x) => x.componentName, - ); - maths5 = stateVariables["/ml5b"].activeChildren.map( - (x) => x.componentName, - ); - maths6 = stateVariables["/ml6b"].activeChildren.map( - (x) => x.componentName, - ); - } else { - maths1 = stateVariables["/pms1a"].activeChildren.map( - (x) => x.componentName, - ); - maths2 = stateVariables["/pms2a"].activeChildren.map( - (x) => x.componentName, - ); - maths3 = stateVariables["/pms3a"].activeChildren.map( - (x) => x.componentName, - ); - maths4 = stateVariables["/pms4a"].activeChildren.map( - (x) => x.componentName, - ); - maths5 = stateVariables["/pms5a"].activeChildren.map( - (x) => x.componentName, - ); - maths6 = stateVariables["/pms6a"].activeChildren.map( - (x) => x.componentName, - ); - } - - cy.get(cesc2("#" + maths1[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#" + maths1[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths1[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.5"); - }); - cy.get(cesc2("#" + maths1[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.252⋅10−13"); - }); - cy.get(cesc2("#" + maths1[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#" + maths2[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#" + maths2[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths2[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.5000"); - }); - cy.get(cesc2("#" + maths2[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.252⋅10−13"); - }); - cy.get(cesc2("#" + maths2[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - - cy.get(cesc2("#" + maths3[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.154"); - }); - cy.get(cesc2("#" + maths3[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths3[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.5"); - }); - cy.get(cesc2("#" + maths3[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#" + maths3[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#" + maths4[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.154"); - }); - cy.get(cesc2("#" + maths4[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths4[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.500"); - }); - cy.get(cesc2("#" + maths4[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - cy.get(cesc2("#" + maths4[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - - cy.get(cesc2("#" + maths5[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#" + maths5[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#" + maths5[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.5"); - }); - cy.get(cesc2("#" + maths5[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.25⋅10−13"); - }); - cy.get(cesc2("#" + maths5[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("6⋅10−21"); - }); - - cy.get(cesc2("#" + maths6[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#" + maths6[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#" + maths6[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.5000"); - }); - cy.get(cesc2("#" + maths6[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.25⋅10−13"); - }); - cy.get(cesc2("#" + maths6[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("6.00⋅10−21"); - }); - } - }); - }); - - it("mathlist and rounding, ignore math children attributes", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let maths1 = stateVariables["/ml1"].activeChildren.map( - (x) => x.componentName, - ); - let maths2 = stateVariables["/ml2"].activeChildren.map( - (x) => x.componentName, - ); - let maths3 = stateVariables["/ml3"].activeChildren.map( - (x) => x.componentName, - ); - let maths5 = stateVariables["/ml5"].activeChildren.map( - (x) => x.componentName, - ); - let maths6 = stateVariables["/ml6"].activeChildren.map( - (x) => x.componentName, - ); - let maths9 = stateVariables["/ml9"].activeChildren.map( - (x) => x.componentName, - ); - - cy.get(cesc2("#" + maths1[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.15"); - }); - cy.get(cesc2("#" + maths1[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.52"); - }); - cy.get(cesc2("#" + maths1[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#" + maths1[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#" + maths1[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#" + maths2[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#" + maths2[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths2[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#" + maths2[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#" + maths2[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#" + maths3[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#" + maths3[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths3[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.000"); - }); - cy.get(cesc2("#" + maths3[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - cy.get(cesc2("#" + maths3[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - - cy.get(cesc2("#" + maths5[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#" + maths5[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#" + maths5[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#" + maths5[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#" + maths5[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#" + maths6[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#" + maths6[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#" + maths6[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.0000"); - }); - cy.get(cesc2("#" + maths6[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.0000"); - }); - cy.get(cesc2("#" + maths6[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.0000"); - }); - - cy.get(cesc2("#" + maths9[0]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#" + maths9[1]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#" + maths9[2]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#" + maths9[3]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.252⋅10−16"); - }); - cy.get(cesc2("#" + maths9[4]) + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("6⋅10−21"); - }); - }); - }); - - it("mathlist and rounding, ignore number children attributes", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.get(cesc2("#/n11") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.15"); - }); - cy.get(cesc2("#/n12") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.52"); - }); - cy.get(cesc2("#/n13") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#/n14") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#/n15") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#/n21") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#/n22") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#/n23") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#/n24") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#/n25") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#/n31") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#/n32") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#/n33") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.000"); - }); - cy.get(cesc2("#/n34") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - cy.get(cesc2("#/n35") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.000"); - }); - - cy.get(cesc2("#/n51") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#/n52") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#/n53") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#/n54") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - cy.get(cesc2("#/n55") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0"); - }); - - cy.get(cesc2("#/n61") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345.1535"); - }); - cy.get(cesc2("#/n62") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.5234"); - }); - cy.get(cesc2("#/n63") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.0000"); - }); - cy.get(cesc2("#/n64") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.0000"); - }); - cy.get(cesc2("#/n65") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("0.0000"); - }); - - cy.get(cesc2("#/n91") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("2345"); - }); - cy.get(cesc2("#/n92") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("3.523"); - }); - cy.get(cesc2("#/n93") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5"); - }); - cy.get(cesc2("#/n94") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("5.252⋅10−16"); - }); - cy.get(cesc2("#/n95") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text).eq("6⋅10−21"); - }); - }); - - it("mathlist and rounding, copy and override", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

34.245023482352345 245.23823402358234234

-

-

-

-

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.get(cesc("#\\/ml1") + " .mjx-mrow") - .eq(0) - .should("have.text", "34.25"); - cy.get(cesc("#\\/ml1") + " .mjx-mrow") - .eq(1) - .should("have.text", "245.24"); - - cy.get(cesc("#\\/ml1Dig6") + " .mjx-mrow") - .eq(0) - .should("have.text", "34.245"); - cy.get(cesc("#\\/ml1Dig6") + " .mjx-mrow") - .eq(1) - .should("have.text", "245.238"); - cy.get(cesc("#\\/ml1Dig6a") + " .mjx-mrow") - .eq(0) - .should("have.text", "34.245"); - cy.get(cesc("#\\/ml1Dig6a") + " .mjx-mrow") - .eq(1) - .should("have.text", "245.238"); - - cy.get(cesc("#\\/ml1Dec6") + " .mjx-mrow") - .eq(0) - .should("have.text", "34.245023"); - cy.get(cesc("#\\/ml1Dec6") + " .mjx-mrow") - .eq(1) - .should("have.text", "245.238234"); - cy.get(cesc("#\\/ml1Dec6a") + " .mjx-mrow") - .eq(0) - .should("have.text", "34.245023"); - cy.get(cesc("#\\/ml1Dec6a") + " .mjx-mrow") - .eq(1) - .should("have.text", "245.238234"); - }); - - it("mathlist adapts to math and text", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - a bc - -

Math list as math: $_mathlist1

-

Math list as text: $_mathlist1

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_math1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a"); - }); - cy.get(cesc("#\\/_math2") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("b"); - }); - cy.get(cesc("#\\/_math3") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("c"); - }); - cy.get(cesc("#\\/_math4") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("a,b,c"); - }); - cy.get(cesc("#\\/_text2")).should("have.text", "a, b, c"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_math4"].stateValues.value).eqls([ - "list", - "a", - "b", - "c", - ]); - expect(stateVariables["/_text2"].stateValues.value).eq("a, b, c"); - }); - }); - - it("mathlist adapts to numberlist", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - 9 87 - -

$ml

-

Change second number: $nl.number2

- -

Change 1st and 3rd number via point: ($nl.number1,$nl.math3)

- - `, - }, - "*", - ); - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "9"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "8"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "7"); - - cy.get(cesc2("#/nl")).should("have.text", "9, 8, 7"); - - cy.get(cesc2("#/mi1") + " textarea").type("{end}3{enter}", { - force: true, - }); - - cy.get(cesc2("#/nl")).should("have.text", "9, 83, 7"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "9"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "83"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "7"); - - cy.get(cesc2("#/mi2") + " textarea").type( - "{end}{leftarrow}{backspace}{backspace}{backspace}-1,2{enter}", - { force: true }, - ); - - cy.get(cesc2("#/nl")).should("have.text", "-1, 83, 2"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "−1"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(1) - .should("have.text", "83"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(2) - .should("have.text", "2"); - }); -}); diff --git a/packages/test-cypress/cypress/e2e/tagSpecific/numberlist.cy.js b/packages/test-cypress/cypress/e2e/tagSpecific/numberlist.cy.js deleted file mode 100644 index 65035e012..000000000 --- a/packages/test-cypress/cypress/e2e/tagSpecific/numberlist.cy.js +++ /dev/null @@ -1,1669 +0,0 @@ -import { cesc, cesc2 } from "@doenet/utils"; - -describe("Numberlist Tag Tests", function () { - beforeEach(() => { - cy.clearIndexedDB(); - cy.visit("/"); - }); - - it("numberlist from string", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

5 1+1 pi

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "5, 2, 3.14"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq(5); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq(2); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[2] - .componentName - ].stateValues.value, - ).closeTo(Math.PI, 14); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - expect( - stateVariables["/_numberlist1"].stateValues.numbers[2], - ).closeTo(Math.PI, 14); - }); - }); - - it("numberlist with error in string", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

5 _ 1+1

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "5, NaN, 2"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq(5); - assert.isNaN( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[2] - .componentName - ].stateValues.value, - ).eq(2); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 5, - ); - assert.isNaN( - stateVariables["/_numberlist1"].stateValues.numbers[1], - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - 2, - ); - }); - }); - - it("numberlist with number children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 5 - 1+1 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "5, 2"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq(5); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq(2); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - }); - }); - - it("numberlist with number and string children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- -1 8/2 - 5 9 - 1+1 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "-1, 4, 5, 9, 2"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq(-1); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq(4); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[2] - .componentName - ].stateValues.value, - ).eq(5); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[3] - .componentName - ].stateValues.value, - ).eq(9); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[4] - .componentName - ].stateValues.value, - ).eq(2); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - -1, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 4, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - 9, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - 2, - ); - }); - }); - - it("numberlist with math and number children", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 5 - 1+1 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "5, 2"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[0] - .componentName - ].stateValues.value, - ).eq(5); - expect( - stateVariables[ - stateVariables["/_numberlist1"].activeChildren[1] - .componentName - ].stateValues.value, - ).eq(2); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - }); - }); - - it("numberlist with numberlist children, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 1 - 2 3 - 4 - - - 5 - 6 7 - - 8 9 - -

- - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "1, 2, 3, 4, 5, 6, 7, 8, 9", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 1, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - 3, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - 4, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[5]).eq( - 6, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[6]).eq( - 7, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[7]).eq( - 8, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[8]).eq( - 9, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[0]).eq( - 2, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[1]).eq( - 3, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[1]).eq( - 6, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[2]).eq( - 7, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[3]).eq( - 8, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[4]).eq( - 9, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[1]).eq( - 6, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[2]).eq( - 7, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[0]).eq( - 6, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[1]).eq( - 7, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[0]).eq( - 8, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[1]).eq( - 9, - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_mathinput1") + " textarea").type( - "{end}{backspace}-11{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput2") + " textarea").type( - "{end}{backspace}-12{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput3") + " textarea").type( - "{end}{backspace}-13{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput4") + " textarea").type( - "{end}{backspace}-14{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput5") + " textarea").type( - "{end}{backspace}-15{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput6") + " textarea").type( - "{end}{backspace}-16{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput7") + " textarea").type( - "{end}{backspace}-17{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput8") + " textarea").type( - "{end}{backspace}-18{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput9") + " textarea").type( - "{end}{backspace}-19{enter}", - { force: true }, - ); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "-11, -12, -13, -14, -15, -16, -17, -18, -19", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - -11, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - -12, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - -13, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - -14, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - -15, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[5]).eq( - -16, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[6]).eq( - -17, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[7]).eq( - -18, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[8]).eq( - -19, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[0]).eq( - -12, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[1]).eq( - -13, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[0]).eq( - -15, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[1]).eq( - -16, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[2]).eq( - -17, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[3]).eq( - -18, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[4]).eq( - -19, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[0]).eq( - -15, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[1]).eq( - -16, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[2]).eq( - -17, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[0]).eq( - -16, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[1]).eq( - -17, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[0]).eq( - -18, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[1]).eq( - -19, - ); - }); - }); - - it("numberlist with numberlist children and sugar, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 1 - 2 3 - 4 - - - 5 - 6 7 - - 8 9 - -

- - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "1, 2, 3, 4, 5, 6, 7, 8, 9", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 1, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - 3, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - 4, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - 5, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[5]).eq( - 6, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[6]).eq( - 7, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[7]).eq( - 8, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[8]).eq( - 9, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[0]).eq( - 2, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[1]).eq( - 3, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[1]).eq( - 6, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[2]).eq( - 7, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[3]).eq( - 8, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[4]).eq( - 9, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[0]).eq( - 5, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[1]).eq( - 6, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[2]).eq( - 7, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[0]).eq( - 6, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[1]).eq( - 7, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[0]).eq( - 8, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[1]).eq( - 9, - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_mathinput1") + " textarea").type( - "{end}{backspace}-11{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput2") + " textarea").type( - "{end}{backspace}-12{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput3") + " textarea").type( - "{end}{backspace}-13{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput4") + " textarea").type( - "{end}{backspace}-14{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput5") + " textarea").type( - "{end}{backspace}-15{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput6") + " textarea").type( - "{end}{backspace}-16{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput7") + " textarea").type( - "{end}{backspace}-17{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput8") + " textarea").type( - "{end}{backspace}-18{enter}", - { force: true }, - ); - cy.get(cesc("#\\/_mathinput9") + " textarea").type( - "{end}{backspace}-19{enter}", - { force: true }, - ); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "-11, -12, -13, -14, -15, -16, -17, -18, -19", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - -11, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - -12, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - -13, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - -14, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - -15, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[5]).eq( - -16, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[6]).eq( - -17, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[7]).eq( - -18, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[8]).eq( - -19, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[0]).eq( - -12, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[1]).eq( - -13, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[0]).eq( - -15, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[1]).eq( - -16, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[2]).eq( - -17, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[3]).eq( - -18, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[4]).eq( - -19, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[0]).eq( - -15, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[1]).eq( - -16, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[2]).eq( - -17, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[0]).eq( - -16, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[1]).eq( - -17, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[0]).eq( - -18, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[1]).eq( - -19, - ); - }); - }); - - it("numberlist with self references", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 1 - 2 3 - - - - - 4 5 - - - - - - - $mid -

- - - - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let vals = [1, 2, 3, 4, 5]; - let mapping = [0, 1, 2, 2, 0, 3, 4, 1, 0, 0, 3, 4]; - let mv = (i) => vals[mapping[i]]; - - let numbers = stateVariables["/_numberlist1"].stateValues.numbers; - - let mathinputAnchors = []; - for (let i in mapping) { - mathinputAnchors.push( - cesc(`#\\/_mathinput${Number(i) + 1}`) + ` textarea`, - ); - } - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - mapping.map((x) => vals[x]).join(", "), - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - for (let i in mapping) { - expect(numbers[i]).eq(mv(i)); - } - }); - - cy.log("change values"); - - for (let changeInd in mapping) { - cy.window().then(async (win) => { - vals[mapping[changeInd]] = 100 + Number(changeInd); - cy.get(mathinputAnchors[changeInd]).type( - "{ctrl+home}{shift+end}{backspace}" + - (100 + Number(changeInd)) + - "{enter}", - { force: true }, - ); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - mapping.map((x) => vals[x]).join(", "), - ); - - cy.log( - "Test internal values are set to the correct values", - ); - cy.window().then(async (win) => { - let stateVariables = - await win.returnAllStateVariables1(); - let numbers = - stateVariables["/_numberlist1"].stateValues.numbers; - for (let i in mapping) { - expect(numbers[i]).eq(mv(i)); - } - }); - }); - } - }); - }); - - it("numberlist with maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 1 - 2 3 4 5 - 6 - - - 7 - 8 9 - - 10 11 12 - -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should("have.text", "1, 2, 3, 6, 7, 8, 10"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables["/_numberlist1"].stateValues.numbers.length, - ).eq(7); - expect(stateVariables["/_numberlist1"].stateValues.numbers[0]).eq( - 1, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[1]).eq( - 2, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[2]).eq( - 3, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[3]).eq( - 6, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[4]).eq( - 7, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[5]).eq( - 8, - ); - expect(stateVariables["/_numberlist1"].stateValues.numbers[6]).eq( - 10, - ); - expect( - stateVariables["/_numberlist2"].stateValues.numbers.length, - ).eq(2); - expect(stateVariables["/_numberlist2"].stateValues.numbers[0]).eq( - 2, - ); - expect(stateVariables["/_numberlist2"].stateValues.numbers[1]).eq( - 3, - ); - expect( - stateVariables["/_numberlist3"].stateValues.numbers.length, - ).eq(4); - expect(stateVariables["/_numberlist3"].stateValues.numbers[0]).eq( - 7, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[1]).eq( - 8, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[2]).eq( - 10, - ); - expect(stateVariables["/_numberlist3"].stateValues.numbers[3]).eq( - 11, - ); - expect( - stateVariables["/_numberlist4"].stateValues.numbers.length, - ).eq(2); - expect(stateVariables["/_numberlist4"].stateValues.numbers[0]).eq( - 7, - ); - expect(stateVariables["/_numberlist4"].stateValues.numbers[1]).eq( - 8, - ); - expect( - stateVariables["/_numberlist5"].stateValues.numbers.length, - ).eq(2); - expect(stateVariables["/_numberlist5"].stateValues.numbers[0]).eq( - 8, - ); - expect(stateVariables["/_numberlist5"].stateValues.numbers[1]).eq( - 9, - ); - expect( - stateVariables["/_numberlist6"].stateValues.numbers.length, - ).eq(3); - expect(stateVariables["/_numberlist6"].stateValues.numbers[0]).eq( - 10, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[1]).eq( - 11, - ); - expect(stateVariables["/_numberlist6"].stateValues.numbers[2]).eq( - 12, - ); - }); - }); - - it("dynamic maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

1 2 3 4 5

-

$nl1{maxNumber="$mn2" name="nl2"}

-

Maximum number 1:

-

Maximum number 2:

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")).should("have.text", "1, 2"); - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3, 4, 5"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl1"].stateValues.numbers).eqls([1, 2]); - expect(stateVariables["/nl2"].stateValues.numbers).eqls([ - 1, 2, 3, 4, 5, - ]); - }); - }); - - cy.log("clear first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea") - .type("{end}{backspace}", { force: true }) - .blur(); - cy.get(cesc("#\\/_p1")).should("have.text", "1, 2, 3, 4, 5"); - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3, 4, 5"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl1"].stateValues.numbers).eqls([ - 1, 2, 3, 4, 5, - ]); - expect(stateVariables["/nl2"].stateValues.numbers).eqls([ - 1, 2, 3, 4, 5, - ]); - }); - - cy.log("number in second maxnum"); - cy.get(cesc("#\\/mn2") + " textarea").type("3{enter}", { force: true }); - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3"); - cy.get(cesc("#\\/_p1")).should("have.text", "1, 2, 3, 4, 5"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl1"].stateValues.numbers).eqls([ - 1, 2, 3, 4, 5, - ]); - expect(stateVariables["/nl2"].stateValues.numbers).eqls([1, 2, 3]); - }); - - cy.log("number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("4{enter}", { force: true }); - cy.get(cesc("#\\/_p1")).should("have.text", "1, 2, 3, 4"); - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl1"].stateValues.numbers).eqls([ - 1, 2, 3, 4, - ]); - expect(stateVariables["/nl2"].stateValues.numbers).eqls([1, 2, 3]); - }); - - cy.log("change number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - cy.get(cesc("#\\/_p1")).should("have.text", "1"); - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl1"].stateValues.numbers).eqls([1]); - expect(stateVariables["/nl2"].stateValues.numbers).eqls([1, 2, 3]); - }); - }); - - it("maxNumber with numberlist or mathlist child", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - - - -

1 2 3

-

1 2 3

-

1 2 3

- -

$nl.numbers

-

$nlnl.numbers

-

$nlml.numbers

- -

$nl

-

$nlnl

-

$nlml

- `, - }, - "*", - ); - }); - - cy.get(cesc2("#/pnl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pnlnl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pnlml")).should("have.text", "1, 2"); - cy.get(cesc2("#/pnumbersnl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pnumbersnlnl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pnumbersnlml")).should("have.text", "1, 2"); - cy.get(cesc2("#/pcopynl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pcopynlnl")).should("have.text", "1, 2"); - cy.get(cesc2("#/pcopynlml")).should("have.text", "1, 2"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl"].stateValues.numbers).eqls([1, 2]); - expect(stateVariables["/nl"].stateValues.text).eqls("1, 2"); - expect(stateVariables["/nlnl"].stateValues.numbers).eqls([1, 2]); - expect(stateVariables["/nlnl"].stateValues.text).eqls("1, 2"); - expect(stateVariables["/nlml"].stateValues.numbers).eqls([1, 2]); - expect(stateVariables["/nlml"].stateValues.text).eqls("1, 2"); - }); - - cy.get(cesc2("#/maxn") + " textarea").type("{end}{backspace}4{enter}", { - force: true, - }); - - cy.get(cesc2("#/pnl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pnlnl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pnlml")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pnumbersnl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pnumbersnlnl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pnumbersnlml")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pcopynl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pcopynlnl")).should("have.text", "1, 2, 3"); - cy.get(cesc2("#/pcopynlml")).should("have.text", "1, 2, 3"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl"].stateValues.numbers).eqls([1, 2, 3]); - expect(stateVariables["/nl"].stateValues.text).eqls("1, 2, 3"); - expect(stateVariables["/nlnl"].stateValues.numbers).eqls([1, 2, 3]); - expect(stateVariables["/nlnl"].stateValues.text).eqls("1, 2, 3"); - expect(stateVariables["/nlml"].stateValues.numbers).eqls([1, 2, 3]); - expect(stateVariables["/nlml"].stateValues.text).eqls("1, 2, 3"); - }); - - cy.get(cesc2("#/maxn") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - - cy.get(cesc2("#/pnl")).should("have.text", "1"); - cy.get(cesc2("#/pnlnl")).should("have.text", "1"); - cy.get(cesc2("#/pnlml")).should("have.text", "1"); - cy.get(cesc2("#/pnumbersnl")).should("have.text", "1"); - cy.get(cesc2("#/pnumbersnlnl")).should("have.text", "1"); - cy.get(cesc2("#/pnumbersnlml")).should("have.text", "1"); - cy.get(cesc2("#/pcopynl")).should("have.text", "1"); - cy.get(cesc2("#/pcopynlnl")).should("have.text", "1"); - cy.get(cesc2("#/pcopynlml")).should("have.text", "1"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/nl"].stateValues.numbers).eqls([1]); - expect(stateVariables["/nl"].stateValues.text).eqls("1"); - expect(stateVariables["/nlnl"].stateValues.numbers).eqls([1]); - expect(stateVariables["/nlnl"].stateValues.text).eqls("1"); - expect(stateVariables["/nlml"].stateValues.numbers).eqls([1]); - expect(stateVariables["/nlml"].stateValues.text).eqls("1"); - }); - }); - - it("numberlist within numberlists, with child hide", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

1 2 3

- -

- -

- 4 - - 5 - $numberlist1a -

- -

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - - cy.get(cesc("#\\/_p1")).should("have.text", ""); - - cy.get(cesc("#\\/_p2")).should("have.text", "1, 2, 3"); - - cy.get(cesc("#\\/_p3")).should("have.text", "4, 1, 2, 3, 1, 2, 3"); - - cy.get(cesc("#\\/_p4")).should("have.text", "4, 1, 2, 3, 1"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - expect( - stateVariables["/numberlist1"].stateValues.numbers.length, - ).eq(3); - expect(stateVariables["/numberlist1"].stateValues.numbers[0]).eq(1); - expect(stateVariables["/numberlist1"].stateValues.numbers[1]).eq(2); - expect(stateVariables["/numberlist1"].stateValues.numbers[2]).eq(3); - expect( - stateVariables["/numberlist1a"].stateValues.numbers.length, - ).eq(3); - expect(stateVariables["/numberlist1a"].stateValues.numbers[0]).eq( - 1, - ); - expect(stateVariables["/numberlist1a"].stateValues.numbers[1]).eq( - 2, - ); - expect(stateVariables["/numberlist1a"].stateValues.numbers[2]).eq( - 3, - ); - expect( - stateVariables["/numberlist2"].stateValues.numbers.length, - ).eq(8); - expect(stateVariables["/numberlist2"].stateValues.numbers[0]).eq(4); - expect(stateVariables["/numberlist2"].stateValues.numbers[1]).eq(1); - expect(stateVariables["/numberlist2"].stateValues.numbers[2]).eq(2); - expect(stateVariables["/numberlist2"].stateValues.numbers[3]).eq(3); - expect(stateVariables["/numberlist2"].stateValues.numbers[4]).eq(5); - expect(stateVariables["/numberlist2"].stateValues.numbers[5]).eq(1); - expect(stateVariables["/numberlist2"].stateValues.numbers[6]).eq(2); - expect(stateVariables["/numberlist2"].stateValues.numbers[7]).eq(3); - expect( - stateVariables["/numberlist3"].stateValues.numbers.length, - ).eq(6); - expect(stateVariables["/numberlist3"].stateValues.numbers[0]).eq(4); - expect(stateVariables["/numberlist3"].stateValues.numbers[1]).eq(1); - expect(stateVariables["/numberlist3"].stateValues.numbers[2]).eq(2); - expect(stateVariables["/numberlist3"].stateValues.numbers[3]).eq(3); - expect(stateVariables["/numberlist3"].stateValues.numbers[4]).eq(5); - expect(stateVariables["/numberlist3"].stateValues.numbers[5]).eq(1); - }); - }); - - it("numberlist and rounding, from strings", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

-

2345.1535268 3.52343 0.5 0.00000000000052523 0.000000000000000000006

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let numbers1 = stateVariables["/_numberlist1"].activeChildren.map( - (x) => x.componentName, - ); - let numbers2 = stateVariables["/_numberlist2"].activeChildren.map( - (x) => x.componentName, - ); - let numbers3 = stateVariables["/_numberlist3"].activeChildren.map( - (x) => x.componentName, - ); - let numbers4 = stateVariables["/_numberlist4"].activeChildren.map( - (x) => x.componentName, - ); - let numbers5 = stateVariables["/_numberlist5"].activeChildren.map( - (x) => x.componentName, - ); - let numbers6 = stateVariables["/_numberlist6"].activeChildren.map( - (x) => x.componentName, - ); - - cy.get(cesc2("#" + numbers1[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers1[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers1[2])).should("have.text", "0.5"); - cy.get(cesc2("#" + numbers1[3])).should( - "have.text", - "5.252 * 10^(-13)", - ); - cy.get(cesc2("#" + numbers1[4])).should("have.text", "0"); - - cy.get(cesc2("#" + numbers2[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers2[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers2[2])).should("have.text", "0.5000"); - cy.get(cesc2("#" + numbers2[3])).should( - "have.text", - "5.252 * 10^(-13)", - ); - cy.get(cesc2("#" + numbers2[4])).should("have.text", "0.000"); - - cy.get(cesc2("#" + numbers3[0])).should("have.text", "2345.154"); - cy.get(cesc2("#" + numbers3[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers3[2])).should("have.text", "0.5"); - cy.get(cesc2("#" + numbers3[3])).should("have.text", "0"); - cy.get(cesc2("#" + numbers3[4])).should("have.text", "0"); - - cy.get(cesc2("#" + numbers4[0])).should("have.text", "2345.154"); - cy.get(cesc2("#" + numbers4[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers4[2])).should("have.text", "0.500"); - cy.get(cesc2("#" + numbers4[3])).should("have.text", "0.000"); - cy.get(cesc2("#" + numbers4[4])).should("have.text", "0.000"); - - cy.get(cesc2("#" + numbers5[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers5[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers5[2])).should("have.text", "0.5"); - cy.get(cesc2("#" + numbers5[3])).should( - "have.text", - "5.252 * 10^(-13)", - ); - cy.get(cesc2("#" + numbers5[4])).should( - "have.text", - "6 * 10^(-21)", - ); - - cy.get(cesc2("#" + numbers6[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers6[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers6[2])).should("have.text", "0.5000"); - cy.get(cesc2("#" + numbers6[3])).should( - "have.text", - "5.252 * 10^(-13)", - ); - cy.get(cesc2("#" + numbers6[4])).should( - "have.text", - "6.000 * 10^(-21)", - ); - }); - }); - - it("numberlist and rounding, number children attributes ignored", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let numbers1 = stateVariables["/ml1"].activeChildren.map( - (x) => x.componentName, - ); - let numbers2 = stateVariables["/ml2"].activeChildren.map( - (x) => x.componentName, - ); - let numbers3 = stateVariables["/ml3"].activeChildren.map( - (x) => x.componentName, - ); - let numbers5 = stateVariables["/ml5"].activeChildren.map( - (x) => x.componentName, - ); - let numbers6 = stateVariables["/ml6"].activeChildren.map( - (x) => x.componentName, - ); - let numbers9 = stateVariables["/ml9"].activeChildren.map( - (x) => x.componentName, - ); - - cy.get(cesc2("#" + numbers1[0])).should("have.text", "2345.15"); - cy.get(cesc2("#" + numbers1[1])).should("have.text", "3.52"); - cy.get(cesc2("#" + numbers1[2])).should("have.text", "5"); - cy.get(cesc2("#" + numbers1[3])).should("have.text", "0"); - cy.get(cesc2("#" + numbers1[4])).should("have.text", "0"); - - cy.get(cesc2("#" + numbers2[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers2[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers2[2])).should("have.text", "5"); - cy.get(cesc2("#" + numbers2[3])).should("have.text", "0"); - cy.get(cesc2("#" + numbers2[4])).should("have.text", "0"); - - cy.get(cesc2("#" + numbers3[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers3[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers3[2])).should("have.text", "5.000"); - cy.get(cesc2("#" + numbers3[3])).should("have.text", "0.000"); - cy.get(cesc2("#" + numbers3[4])).should("have.text", "0.000"); - - cy.get(cesc2("#" + numbers5[0])).should("have.text", "2345.1535"); - cy.get(cesc2("#" + numbers5[1])).should("have.text", "3.5234"); - cy.get(cesc2("#" + numbers5[2])).should("have.text", "5"); - cy.get(cesc2("#" + numbers5[3])).should("have.text", "0"); - cy.get(cesc2("#" + numbers5[4])).should("have.text", "0"); - - cy.get(cesc2("#" + numbers6[0])).should("have.text", "2345.1535"); - cy.get(cesc2("#" + numbers6[1])).should("have.text", "3.5234"); - cy.get(cesc2("#" + numbers6[2])).should("have.text", "5.0000"); - cy.get(cesc2("#" + numbers6[3])).should("have.text", "0.0000"); - cy.get(cesc2("#" + numbers6[4])).should("have.text", "0.0000"); - - cy.get(cesc2("#" + numbers9[0])).should("have.text", "2345"); - cy.get(cesc2("#" + numbers9[1])).should("have.text", "3.523"); - cy.get(cesc2("#" + numbers9[2])).should("have.text", "5"); - cy.get(cesc2("#" + numbers9[3])).should( - "have.text", - "5.252 * 10^(-16)", - ); - cy.get(cesc2("#" + numbers9[4])).should( - "have.text", - "6 * 10^(-21)", - ); - }); - }); - - it("numberlist and rounding, math children attributes ignored", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

-

- 2345.1535268 - 3.52343 - 5 - 0.00000000000000052523 - 0.000000000000000000006 -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.get(cesc2("#/n11")).should("have.text", "2345.15"); - cy.get(cesc2("#/n12")).should("have.text", "3.52"); - cy.get(cesc2("#/n13")).should("have.text", "5"); - cy.get(cesc2("#/n14")).should("have.text", "0"); - cy.get(cesc2("#/n15")).should("have.text", "0"); - - cy.get(cesc2("#/n21")).should("have.text", "2345"); - cy.get(cesc2("#/n22")).should("have.text", "3.523"); - cy.get(cesc2("#/n23")).should("have.text", "5"); - cy.get(cesc2("#/n24")).should("have.text", "0"); - cy.get(cesc2("#/n25")).should("have.text", "0"); - - cy.get(cesc2("#/n31")).should("have.text", "2345"); - cy.get(cesc2("#/n32")).should("have.text", "3.523"); - cy.get(cesc2("#/n33")).should("have.text", "5.000"); - cy.get(cesc2("#/n34")).should("have.text", "0.000"); - cy.get(cesc2("#/n35")).should("have.text", "0.000"); - - cy.get(cesc2("#/n51")).should("have.text", "2345.1535"); - cy.get(cesc2("#/n52")).should("have.text", "3.5234"); - cy.get(cesc2("#/n53")).should("have.text", "5"); - cy.get(cesc2("#/n54")).should("have.text", "0"); - cy.get(cesc2("#/n55")).should("have.text", "0"); - - cy.get(cesc2("#/n61")).should("have.text", "2345.1535"); - cy.get(cesc2("#/n62")).should("have.text", "3.5234"); - cy.get(cesc2("#/n63")).should("have.text", "5.0000"); - cy.get(cesc2("#/n64")).should("have.text", "0.0000"); - cy.get(cesc2("#/n65")).should("have.text", "0.0000"); - - cy.get(cesc2("#/n91")).should("have.text", "2345"); - cy.get(cesc2("#/n92")).should("have.text", "3.523"); - cy.get(cesc2("#/n93")).should("have.text", "5"); - cy.get(cesc2("#/n94")).should("have.text", "5.252 * 10^(-16)"); - cy.get(cesc2("#/n95")).should("have.text", "6 * 10^(-21)"); - }); - - it("numberlist and rounding, copy and override", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - -

34.245023482352345 245.23823402358234234

-

-

-

-

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.get(cesc("#\\/ml1")).should("have.text", "34.25, 245.24"); - - cy.get(cesc("#\\/ml1Dig6")).should("have.text", "34.245, 245.238"); - cy.get(cesc("#\\/ml1Dig6a")).should("have.text", "34.245, 245.238"); - - cy.get(cesc("#\\/ml1Dec6")).should( - "have.text", - "34.245023, 245.238234", - ); - cy.get(cesc("#\\/ml1Dec6a")).should( - "have.text", - "34.245023, 245.238234", - ); - }); - - it("numberlist adapts to math and text", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - 1 23 - -

number list as math: $_numberlist1

-

number list as text: $_numberlist1

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_number1")).should("have.text", "1"); - cy.get(cesc("#\\/_number2")).should("have.text", "2"); - cy.get(cesc("#\\/_number3")).should("have.text", "3"); - cy.get(cesc("#\\/_math1") + " .mjx-mrow") - .eq(0) - .invoke("text") - .then((text) => { - expect(text.trim()).equal("1,2,3"); - }); - cy.get(cesc("#\\/_text2")).should("have.text", "1, 2, 3"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_math1"].stateValues.value).eqls([ - "list", - 1, - 2, - 3, - ]); - expect(stateVariables["/_text2"].stateValues.value).eq("1, 2, 3"); - }); - }); - - it("numberlist adapts to mathlist", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - 9 87 - -

$nl

-

Change second math: $ml.math2

- -

Change 1st and 3rd math via point: ($ml.number1,$ml.math3)

- - `, - }, - "*", - ); - }); - - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "9,8,7"); - - cy.get(cesc2("#/nl")).should("have.text", "9, 8, 7"); - - cy.get(cesc2("#/mi1") + " textarea").type("{end}3{enter}", { - force: true, - }); - - cy.get(cesc2("#/nl")).should("have.text", "9, 83, 7"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "9,83,7"); - - cy.get(cesc2("#/mi2") + " textarea").type( - "{end}{leftarrow}{backspace}{backspace}{backspace}-1,2{enter}", - { force: true }, - ); - - cy.get(cesc2("#/nl")).should("have.text", "-1, 83, 2"); - cy.get(cesc2("#/ml") + " .mjx-mrow") - .eq(0) - .should("have.text", "−1,83,2"); - }); -}); From f22f0da853f8b00bb9de0872d618b12e4271b6d7 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 07:57:35 -0500 Subject: [PATCH 06/13] textList tests --- .../src/test/tagSpecific/numberlist.test.ts | 2 +- .../src/test/tagSpecific/textlist.test.ts | 645 ++++++++++++++++++ .../cypress/e2e/tagSpecific/textlist.cy.js | 404 ----------- 3 files changed, 646 insertions(+), 405 deletions(-) create mode 100644 packages/doenetml-worker/src/test/tagSpecific/textlist.test.ts delete mode 100644 packages/test-cypress/cypress/e2e/tagSpecific/textlist.cy.js diff --git a/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts index 66fffbce6..6267a2de5 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/numberlist.test.ts @@ -976,7 +976,7 @@ describe("NumberList tag tests", async () => { expect(stateVariables["/t"].stateValues.value).eq("1, 2, 3"); }); - it("numberList adapts to numberList", async () => { + it("numberList adapts to mathList", async () => { let core = await createTestCore({ doenetML: `

9 87

diff --git a/packages/doenetml-worker/src/test/tagSpecific/textlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/textlist.test.ts new file mode 100644 index 000000000..eb45a21df --- /dev/null +++ b/packages/doenetml-worker/src/test/tagSpecific/textlist.test.ts @@ -0,0 +1,645 @@ +import { describe, expect, it, vi } from "vitest"; +import { createTestCore, returnAllStateVariables } from "../utils/test-core"; +import { updateMathInputValue, updateTextInputValue } from "../utils/actions"; + +const Mock = vi.fn(); +vi.stubGlobal("postMessage", Mock); + +describe("TextList tag tests", async () => { + async function test_textList({ + core, + name, + pName, + text, + texts, + }: { + core: any; + name?: string; + pName?: string; + text?: string; + texts?: any[]; + }) { + const stateVariables = await returnAllStateVariables(core); + + if (text !== undefined && pName !== undefined) { + expect(stateVariables[pName].stateValues.text).eq(text); + } + + if (texts !== undefined && name !== undefined) { + expect(stateVariables[name].stateValues.texts).eqls(texts); + } + } + + it("textList from string", async () => { + let core = await createTestCore({ + doenetML: ` +

a b

+ `, + }); + + await test_textList({ + core, + name: "/tl1", + pName: "/p", + text: "a, b", + texts: ["a", "b"], + }); + }); + + it("textList with text children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + b +

+ +

+ ab +

+ `, + }); + + let text = "a, b"; + let texts = ["a", "b"]; + + await test_textList({ + core, + name: "/tl1", + pName: "/p1", + text, + texts, + }); + + await test_textList({ + core, + name: "/tl2", + pName: "/p2", + text, + texts, + }); + }); + + it("textList with text and string children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ hello there + apple banana strawberry +

+ `, + }); + + await test_textList({ + core, + name: "/tl1", + pName: "/p", + text: "hello, there, apple, banana, strawberry", + texts: ["hello", "there", "apple", "banana", "strawberry"], + }); + }); + + async function test_nested_and_inverse(core: any) { + await test_textList({ + core, + name: "/tl1", + pName: "/p", + text: "1, 2, 3, 4, 5, 6, 7, 8, 9", + texts: ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + }); + + await test_textList({ + core, + name: "/tl2", + texts: ["2", "3"], + }); + await test_textList({ + core, + name: "/tl3", + texts: ["5", "6", "7", "8", "9"], + }); + await test_textList({ + core, + name: "/tl4", + texts: ["5", "6", "7"], + }); + await test_textList({ + core, + name: "/tl5", + texts: ["6", "7"], + }); + await test_textList({ + core, + name: "/tl6", + texts: ["8", "9"], + }); + + // change values + + await updateTextInputValue({ + componentName: "/ti1", + text: "a", + core, + }); + await updateTextInputValue({ + componentName: "/ti2", + text: "b", + core, + }); + await updateTextInputValue({ + componentName: "/ti3", + text: "c", + core, + }); + await updateTextInputValue({ + componentName: "/ti4", + text: "d", + core, + }); + await updateTextInputValue({ + componentName: "/ti5", + text: "e", + core, + }); + await updateTextInputValue({ + componentName: "/ti6", + text: "f", + core, + }); + await updateTextInputValue({ + componentName: "/ti7", + text: "g", + core, + }); + await updateTextInputValue({ + componentName: "/ti8", + text: "h", + core, + }); + await updateTextInputValue({ + componentName: "/ti9", + text: "i", + core, + }); + + await test_textList({ + core, + name: "/tl1", + pName: "/p", + text: "a, b, c, d, e, f, g, h, i", + texts: ["a", "b", "c", "d", "e", "f", "g", "h", "i"], + }); + + await test_textList({ + core, + name: "/tl2", + texts: ["b", "c"], + }); + await test_textList({ + core, + name: "/tl3", + texts: ["e", "f", "g", "h", "i"], + }); + await test_textList({ + core, + name: "/tl4", + texts: ["e", "f", "g"], + }); + await test_textList({ + core, + name: "/tl5", + texts: ["f", "g"], + }); + await test_textList({ + core, + name: "/tl6", + texts: ["h", "i"], + }); + } + + it("textList with textList children, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 1 + 2 3 + 4 + + + 5 + 6 7 + + 8 9 + +

+ + $tl1[1] + $tl1[2] + $tl1[3] + $tl1[4] + $tl1[5] + $tl1[6] + $tl1[7] + $tl1[8] + $tl1[9] + + `, + }); + + await test_nested_and_inverse(core); + }); + + it("textList with textList children and sugar, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ 1 + 2 3 + 4 + + + 5 + 6 7 + + 8 9 + +

+ + $tl1[1] + $tl1[2] + $tl1[3] + $tl1[4] + $tl1[5] + $tl1[6] + $tl1[7] + $tl1[8] + $tl1[9] + `, + }); + + await test_nested_and_inverse(core); + }); + + it("textList with maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

+ a + b c d e + f + + + g + h i + + j k l + +

+ `, + }); + + let vals6 = ["j", "k", "l"]; + let vals5 = ["h", "i"]; + let vals4 = ["g", ...vals5].slice(0, 2); + let vals3 = [...vals4, ...vals6].slice(0, 4); + let vals2 = ["b", "c", "d", "e"].slice(0, 2); + let vals1 = ["a", ...vals2, "f", ...vals3].slice(0, 7); + + let sub_vals = [vals2, vals3, vals4, vals5, vals6]; + + await test_textList({ + core, + name: `/tl1`, + texts: vals1, + pName: "/p", + text: vals1.join(", "), + }); + + for (let i = 0; i < 5; i++) { + let vals = sub_vals[i]; + await test_textList({ + core, + name: `/tl${i + 2}`, + texts: vals, + }); + } + }); + + it("copy textList and overwrite maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

1 2 3 4 5

+

$tl1{maxNumber="3" name="tl2"}

+

$tl2{maxNumber="" name="tl3"}

+ +

1 2 3 4 5

+

$tl4{maxNumber="4" name="tl5"}

+

$tl5{maxNumber="" name="tl6"}

+ `, + }); + + let list = ["1", "2", "3", "4", "5"]; + + await test_textList({ + core, + name: "/tl1", + texts: list, + pName: "/p1", + text: list.join(", "), + }); + await test_textList({ + core, + name: "/tl2", + texts: list.slice(0, 3), + pName: "/p2", + text: list.slice(0, 3).join(", "), + }); + await test_textList({ + core, + name: "/tl3", + texts: list, + pName: "/p3", + text: list.join(", "), + }); + await test_textList({ + core, + name: "/tl4", + texts: list.slice(0, 3), + pName: "/p4", + text: list.slice(0, 3).join(", "), + }); + await test_textList({ + core, + name: "/tl5", + texts: list.slice(0, 4), + pName: "/p5", + text: list.slice(0, 4).join(", "), + }); + await test_textList({ + core, + name: "/tl6", + texts: list, + pName: "/p6", + text: list.join(", "), + }); + }); + + it("dynamic maximum number", async () => { + let core = await createTestCore({ + doenetML: ` + +

Maximum number 1:

+

Maximum number 2:

+
+

cat dog monkey dragon horse rat

+

$tl1{maxNumber="$mn2" name="tl2"}

+

$tl2{name="tl3"}

+

$tl3{name="tl4" maxNumber=""}

+
+
+ + `, + }); + + let list = ["cat", "dog", "monkey", "dragon", "horse", "rat"]; + + async function check_items(max1, max2) { + for (let pre of ["", "/sec2"]) { + await test_textList({ + core, + name: `${pre}/tl1`, + texts: list.slice(0, max1), + pName: `${pre}/p1`, + text: list.slice(0, max1).join(", "), + }); + await test_textList({ + core, + name: `${pre}/tl2`, + texts: list.slice(0, max2), + pName: `${pre}/p2`, + text: list.slice(0, max2).join(", "), + }); + await test_textList({ + core, + name: `${pre}/tl3`, + texts: list.slice(0, max2), + pName: `${pre}/p3`, + text: list.slice(0, max2).join(", "), + }); + await test_textList({ + core, + name: `${pre}/tl4`, + texts: list, + pName: `${pre}/p4`, + text: list.join(", "), + }); + } + } + + let max1 = 2, + max2 = Infinity; + + await check_items(max1, max2); + + max1 = Infinity; + await updateMathInputValue({ latex: "", componentName: "/mn1", core }); + await check_items(max1, max2); + + max2 = 3; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + + max1 = 4; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max1 = 1; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max2 = 10; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + }); + + it("maxNumber with mathList, numberList, or textList child", async () => { + let core = await createTestCore({ + doenetML: ` + + + +

1 2 3

+

1 2 3

+

1 2 3

+

1 2 3

+ +

$tl{name="tlCopy"}

+

$tlTl{name="tlTlCopy"}

+

$tlMl{name="tlMlCopy"}

+

$tlNl{name="tlNlCopy"}

+ `, + }); + + let names = [ + ["/tl", "/pTl"], + ["/tlTl", "/pTlTl"], + ["/tlMl", "/pTlMl"], + ["/tlNl", "/pTlNl"], + ["/tlCopy", "/pCopyMl"], + ["/tlTlCopy", "/pCopyTlTl"], + ["/tlMlCopy", "/pCopyTlMl"], + ["/tlNlCopy", "/pCopyTlNl"], + ]; + async function check_items(maxN: number) { + let texts = ["1", "2", "3"].slice(0, maxN); + let text = texts.join(", "); + + for (let [m, p] of names) { + await test_textList({ + core, + name: m, + texts, + pName: p, + text, + }); + } + } + + let maxN = 2; + await check_items(maxN); + + maxN = 4; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + + maxN = 1; + await updateMathInputValue({ + latex: maxN.toString(), + componentName: "/maxN", + core, + }); + await check_items(maxN); + }); + + it("textList within textLists, ignore child hide", async () => { + let core = await createTestCore({ + doenetML: ` +

1 2 3

+ +

+ 4 + $tl1 + 5 + $tl1{hide="false"} +

+ +

$tl2{name="tl3" maxNumber="6"}

+ + `, + }); + + await test_textList({ + core, + name: "/tl2", + texts: ["4", "1", "2", "3", "5", "1", "2", "3"], + pName: "/p2", + text: "4, 1, 2, 3, 5, 1, 2, 3", + }); + + await test_textList({ + core, + name: "/tl3", + texts: ["4", "1", "2", "3", "5", "1"], + pName: "/p3", + text: "4, 1, 2, 3, 5, 1", + }); + }); + + it("textList does not force composite replacement, even in boolean", async () => { + let core = await createTestCore({ + doenetML: ` + + $nothing dog = dog + + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/b"].stateValues.value).eq(true); + }); + + it("assignNames", async () => { + let core = await createTestCore({ + doenetML: ` +

cat dog monkey

+

$a, $b, $c

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p1"].stateValues.text).eq("cat, dog, monkey"); + expect(stateVariables["/p2"].stateValues.text).eq("cat, dog, monkey"); + expect(stateVariables["/a"].stateValues.value).eq("cat"); + expect(stateVariables["/b"].stateValues.value).eq("dog"); + expect(stateVariables["/c"].stateValues.value).eq("monkey"); + }); + + it("textList adapts to math and text", async () => { + let core = await createTestCore({ + doenetML: ` + 1 abc3x + +

Text list as math: $tl

+

Text list as text: $tl

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/m"].stateValues.value.tree).eqls([ + "list", + 1, + ["*", "a", "b", "c"], + ["*", 3, "x"], + ]); + expect(stateVariables["/t"].stateValues.value).eq("1, abc, 3x"); + }); + + it("text from textList", async () => { + let core = await createTestCore({ + doenetML: ` + apple banana + +

Text: $tl.text

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/pText"].stateValues.text).eq( + "Text: apple, banana", + ); + }); +}); diff --git a/packages/test-cypress/cypress/e2e/tagSpecific/textlist.cy.js b/packages/test-cypress/cypress/e2e/tagSpecific/textlist.cy.js deleted file mode 100644 index 093714259..000000000 --- a/packages/test-cypress/cypress/e2e/tagSpecific/textlist.cy.js +++ /dev/null @@ -1,404 +0,0 @@ -import { cesc } from "@doenet/utils"; - -describe("TextList Tag Tests", function () { - beforeEach(() => { - cy.clearIndexedDB(); - cy.visit("/"); - }); - - it("textlist within textlists", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` -

a b c

- -

- -

- hello - $_textlist1{hide="false"} - bye - $textlist1a -

- -

- -

$textlist2.text

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_p1")).should("have.text", ""); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c"); - cy.get(cesc("#\\/_p3")).should( - "have.text", - "hello, a, b, c, bye, a, b, c", - ); - cy.get(cesc("#\\/_p4")).should("have.text", "hello, a, b, c, bye, a"); - cy.get(cesc("#\\/_p5")).should( - "have.text", - "hello, a, b, c, bye, a, b, c", - ); - }); - - it("textlist with textlist children, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- a - q r - h - - - b - u v - - i j - -

- - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "a, q, r, h, b, u, v, i, j", - ); - - cy.get(cesc("#\\/_textinput1_input")).should("have.value", "a"); - cy.get(cesc("#\\/_textinput2_input")).should("have.value", "q"); - cy.get(cesc("#\\/_textinput3_input")).should("have.value", "r"); - cy.get(cesc("#\\/_textinput4_input")).should("have.value", "h"); - cy.get(cesc("#\\/_textinput5_input")).should("have.value", "b"); - cy.get(cesc("#\\/_textinput6_input")).should("have.value", "u"); - cy.get(cesc("#\\/_textinput7_input")).should("have.value", "v"); - cy.get(cesc("#\\/_textinput8_input")).should("have.value", "i"); - cy.get(cesc("#\\/_textinput9_input")).should("have.value", "j"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_textlist1"].stateValues.texts[0]).eq("a"); - expect(stateVariables["/_textlist1"].stateValues.texts[1]).eq("q"); - expect(stateVariables["/_textlist1"].stateValues.texts[2]).eq("r"); - expect(stateVariables["/_textlist1"].stateValues.texts[3]).eq("h"); - expect(stateVariables["/_textlist1"].stateValues.texts[4]).eq("b"); - expect(stateVariables["/_textlist1"].stateValues.texts[5]).eq("u"); - expect(stateVariables["/_textlist1"].stateValues.texts[6]).eq("v"); - expect(stateVariables["/_textlist1"].stateValues.texts[7]).eq("i"); - expect(stateVariables["/_textlist1"].stateValues.texts[8]).eq("j"); - expect(stateVariables["/_textlist2"].stateValues.texts[0]).eq("q"); - expect(stateVariables["/_textlist2"].stateValues.texts[1]).eq("r"); - expect(stateVariables["/_textlist3"].stateValues.texts[0]).eq("b"); - expect(stateVariables["/_textlist3"].stateValues.texts[1]).eq("u"); - expect(stateVariables["/_textlist3"].stateValues.texts[2]).eq("v"); - expect(stateVariables["/_textlist3"].stateValues.texts[3]).eq("i"); - expect(stateVariables["/_textlist3"].stateValues.texts[4]).eq("j"); - expect(stateVariables["/_textlist4"].stateValues.texts[0]).eq("b"); - expect(stateVariables["/_textlist4"].stateValues.texts[1]).eq("u"); - expect(stateVariables["/_textlist4"].stateValues.texts[2]).eq("v"); - expect(stateVariables["/_textlist5"].stateValues.texts[0]).eq("u"); - expect(stateVariables["/_textlist5"].stateValues.texts[1]).eq("v"); - expect(stateVariables["/_textlist6"].stateValues.texts[0]).eq("i"); - expect(stateVariables["/_textlist6"].stateValues.texts[1]).eq("j"); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_textinput1_input")).clear().type("1{enter}"); - cy.get(cesc("#\\/_textinput2_input")).clear().type("2{enter}"); - cy.get(cesc("#\\/_textinput3_input")).clear().type("3{enter}"); - cy.get(cesc("#\\/_textinput4_input")).clear().type("4{enter}"); - cy.get(cesc("#\\/_textinput5_input")).clear().type("5{enter}"); - cy.get(cesc("#\\/_textinput6_input")).clear().type("6{enter}"); - cy.get(cesc("#\\/_textinput7_input")).clear().type("7{enter}"); - cy.get(cesc("#\\/_textinput8_input")).clear().type("8{enter}"); - cy.get(cesc("#\\/_textinput9_input")).clear().type("9{enter}"); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "1, 2, 3, 4, 5, 6, 7, 8, 9", - ); - - cy.get(cesc("#\\/_textinput1_input")).should("have.value", "1"); - cy.get(cesc("#\\/_textinput2_input")).should("have.value", "2"); - cy.get(cesc("#\\/_textinput3_input")).should("have.value", "3"); - cy.get(cesc("#\\/_textinput4_input")).should("have.value", "4"); - cy.get(cesc("#\\/_textinput5_input")).should("have.value", "5"); - cy.get(cesc("#\\/_textinput6_input")).should("have.value", "6"); - cy.get(cesc("#\\/_textinput7_input")).should("have.value", "7"); - cy.get(cesc("#\\/_textinput8_input")).should("have.value", "8"); - cy.get(cesc("#\\/_textinput9_input")).should("have.value", "9"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_textlist1"].stateValues.texts[0]).eq("1"); - expect(stateVariables["/_textlist1"].stateValues.texts[1]).eq("2"); - expect(stateVariables["/_textlist1"].stateValues.texts[2]).eq("3"); - expect(stateVariables["/_textlist1"].stateValues.texts[3]).eq("4"); - expect(stateVariables["/_textlist1"].stateValues.texts[4]).eq("5"); - expect(stateVariables["/_textlist1"].stateValues.texts[5]).eq("6"); - expect(stateVariables["/_textlist1"].stateValues.texts[6]).eq("7"); - expect(stateVariables["/_textlist1"].stateValues.texts[7]).eq("8"); - expect(stateVariables["/_textlist1"].stateValues.texts[8]).eq("9"); - expect(stateVariables["/_textlist2"].stateValues.texts[0]).eq("2"); - expect(stateVariables["/_textlist2"].stateValues.texts[1]).eq("3"); - expect(stateVariables["/_textlist3"].stateValues.texts[0]).eq("5"); - expect(stateVariables["/_textlist3"].stateValues.texts[1]).eq("6"); - expect(stateVariables["/_textlist3"].stateValues.texts[2]).eq("7"); - expect(stateVariables["/_textlist3"].stateValues.texts[3]).eq("8"); - expect(stateVariables["/_textlist3"].stateValues.texts[4]).eq("9"); - expect(stateVariables["/_textlist4"].stateValues.texts[0]).eq("5"); - expect(stateVariables["/_textlist4"].stateValues.texts[1]).eq("6"); - expect(stateVariables["/_textlist4"].stateValues.texts[2]).eq("7"); - expect(stateVariables["/_textlist5"].stateValues.texts[0]).eq("6"); - expect(stateVariables["/_textlist5"].stateValues.texts[1]).eq("7"); - expect(stateVariables["/_textlist6"].stateValues.texts[0]).eq("8"); - expect(stateVariables["/_textlist6"].stateValues.texts[1]).eq("9"); - }); - }); - - it("textlist does not force composite replacement, even in boolean", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - $nothing = - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_boolean1")).should("have.text", "true"); - }); - - it("copy textlist and overwrite maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

a b c d e

-

-

- -

a b c d e

-

-

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")).should("have.text", "a, b, c, d, e"); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c"); - cy.get(cesc("#\\/_p3")).should("have.text", "a, b, c, d, e"); - - cy.get(cesc("#\\/_p4")).should("have.text", "a, b, c"); - cy.get(cesc("#\\/_p5")).should("have.text", "a, b, c, d"); - cy.get(cesc("#\\/_p6")).should("have.text", "a, b, c, d, e"); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - ]); - expect(stateVariables["/tl3"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/tl4"].stateValues.texts).eqls([ - "a", - "b", - "c", - ]); - expect(stateVariables["/tl5"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - ]); - expect(stateVariables["/tl6"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - }); - }); - }); - - it("dynamic maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

a b c d e

-

-

Maximum number 1:

-

Maximum number 2:

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")).should("have.text", "a, b"); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c, d, e"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls([ - "a", - "b", - ]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - }); - }); - - cy.log("clear first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea") - .type("{end}{backspace}", { force: true }) - .blur(); - cy.get(cesc("#\\/_p1")).should("have.text", "a, b, c, d, e"); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c, d, e"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - }); - - cy.log("number in second maxnum"); - cy.get(cesc("#\\/mn2") + " textarea").type("3{enter}", { force: true }); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c"); - cy.get(cesc("#\\/_p1")).should("have.text", "a, b, c, d, e"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - "e", - ]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - ]); - }); - - cy.log("number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("4{enter}", { force: true }); - cy.get(cesc("#\\/_p1")).should("have.text", "a, b, c, d"); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls([ - "a", - "b", - "c", - "d", - ]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - ]); - }); - - cy.log("change number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - cy.get(cesc("#\\/_p1")).should("have.text", "a"); - cy.get(cesc("#\\/_p2")).should("have.text", "a, b, c"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/tl1"].stateValues.texts).eqls(["a"]); - expect(stateVariables["/tl2"].stateValues.texts).eqls([ - "a", - "b", - "c", - ]); - }); - }); -}); From c19b20c7dc25c398f6361f7243db5493a6ed3135 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 08:21:29 -0500 Subject: [PATCH 07/13] booleanList tests --- .../src/test/tagSpecific/booleanlist.test.ts | 584 +++++++++++++++ .../cypress/e2e/tagSpecific/booleanlist.cy.js | 686 ------------------ 2 files changed, 584 insertions(+), 686 deletions(-) create mode 100644 packages/doenetml-worker/src/test/tagSpecific/booleanlist.test.ts delete mode 100644 packages/test-cypress/cypress/e2e/tagSpecific/booleanlist.cy.js diff --git a/packages/doenetml-worker/src/test/tagSpecific/booleanlist.test.ts b/packages/doenetml-worker/src/test/tagSpecific/booleanlist.test.ts new file mode 100644 index 000000000..e50cb3852 --- /dev/null +++ b/packages/doenetml-worker/src/test/tagSpecific/booleanlist.test.ts @@ -0,0 +1,584 @@ +import { describe, expect, it, vi } from "vitest"; +import { createTestCore, returnAllStateVariables } from "../utils/test-core"; +import { + updateBooleanInputValue, + updateMathInputValue, +} from "../utils/actions"; + +const Mock = vi.fn(); +vi.stubGlobal("postMessage", Mock); + +describe("BooleanList tag tests", async () => { + async function test_booleanList({ + core, + name, + pName, + text, + booleans, + }: { + core: any; + name?: string; + pName?: string; + text?: string; + booleans?: any[]; + }) { + const stateVariables = await returnAllStateVariables(core); + + if (text !== undefined && pName !== undefined) { + expect(stateVariables[pName].stateValues.text).eq(text); + } + + if (booleans !== undefined && name !== undefined) { + expect(stateVariables[name].stateValues.booleans).eqls(booleans); + } + } + + it("booleanList from string", async () => { + let core = await createTestCore({ + doenetML: ` +

false true

+ `, + }); + + await test_booleanList({ + core, + name: "/bl1", + pName: "/p", + text: "false, true", + booleans: [false, true], + }); + }); + + it("booleanList with boolean children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ false + not false +

+ +

+ falsenot false +

+ `, + }); + + let text = "false, true"; + let booleans = [false, true]; + + await test_booleanList({ + core, + name: "/bl1", + pName: "/p1", + text, + booleans, + }); + + await test_booleanList({ + core, + name: "/bl2", + pName: "/p2", + text, + booleans, + }); + }); + + it("booleanList with boolean and string children", async () => { + let core = await createTestCore({ + doenetML: ` +

+ false true + not false true not true +

+ `, + }); + + await test_booleanList({ + core, + name: "/bl1", + pName: "/p", + text: "false, true, true, true, false", + booleans: [false, true, true, true, false], + }); + }); + + async function test_nested_and_inverse(core: any) { + await test_booleanList({ + core, + name: "/bl1", + pName: "/p", + text: "true, true, false, true, false, false, true, true, false", + booleans: [ + true, + true, + false, + true, + false, + false, + true, + true, + false, + ], + }); + + await test_booleanList({ + core, + name: "/bl2", + booleans: [true, false], + }); + await test_booleanList({ + core, + name: "/bl3", + booleans: [false, false, true, true, false], + }); + await test_booleanList({ + core, + name: "/bl4", + booleans: [false, false, true], + }); + await test_booleanList({ + core, + name: "/bl5", + booleans: [false, true], + }); + await test_booleanList({ + core, + name: "/bl6", + booleans: [true, false], + }); + + // change values + + await updateBooleanInputValue({ + componentName: "/mi1", + boolean: false, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi2", + boolean: false, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi3", + boolean: true, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi4", + boolean: false, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi5", + boolean: true, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi6", + boolean: true, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi7", + boolean: false, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi8", + boolean: false, + core, + }); + await updateBooleanInputValue({ + componentName: "/mi9", + boolean: true, + core, + }); + + await test_booleanList({ + core, + name: "/bl1", + pName: "/p", + text: "false, false, true, false, true, true, false, false, true", + booleans: [ + false, + false, + true, + false, + true, + true, + false, + false, + true, + ], + }); + + await test_booleanList({ + core, + name: "/bl2", + booleans: [false, true], + }); + await test_booleanList({ + core, + name: "/bl3", + booleans: [true, true, false, false, true], + }); + await test_booleanList({ + core, + name: "/bl4", + booleans: [true, true, false], + }); + await test_booleanList({ + core, + name: "/bl5", + booleans: [true, false], + }); + await test_booleanList({ + core, + name: "/bl6", + booleans: [false, true], + }); + } + + it("booleanList with booleanList children, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ true + true false + true + + + false + false true + + true false + +

+ + $bl1[1] + $bl1[2] + $bl1[3] + $bl1[4] + $bl1[5] + $bl1[6] + $bl1[7] + $bl1[8] + $bl1[9] + + `, + }); + + await test_nested_and_inverse(core); + }); + + it("booleanList with booleanList children and sugar, test inverse", async () => { + let core = await createTestCore({ + doenetML: ` +

+ true + true false + true + + + false + false true + + true false + +

+ + $bl1[1] + $bl1[2] + $bl1[3] + $bl1[4] + $bl1[5] + $bl1[6] + $bl1[7] + $bl1[8] + $bl1[9] + `, + }); + + await test_nested_and_inverse(core); + }); + + it("booleanList with maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

+ true + true false true false + false + + + false + false true + + false true true + +

+ `, + }); + + let vals6 = [false, true, true]; + let vals5 = [false, true]; + let vals4 = [false, ...vals5].slice(0, 2); + let vals3 = [...vals4, ...vals6].slice(0, 4); + let vals2 = [true, false, true, false].slice(0, 2); + let vals1 = [true, ...vals2, false, ...vals3].slice(0, 7); + + let sub_vals = [vals2, vals3, vals4, vals5, vals6]; + + await test_booleanList({ + core, + name: `/bl1`, + booleans: vals1, + pName: "/p", + text: vals1.join(", "), + }); + + for (let i = 0; i < 5; i++) { + let vals = sub_vals[i]; + await test_booleanList({ + core, + name: `/bl${i + 2}`, + booleans: vals, + }); + } + }); + + it("copy booleanList and overwrite maximum number", async () => { + let core = await createTestCore({ + doenetML: ` +

true true false false true

+

$bl1{maxNumber="3" name="bl2"}

+

$bl2{maxNumber="" name="bl3"}

+ +

true true false false true

+

$bl4{maxNumber="4" name="bl5"}

+

$bl5{maxNumber="" name="bl6"}

+ `, + }); + + let list = [true, true, false, false, true]; + + await test_booleanList({ + core, + name: "/bl1", + booleans: list, + pName: "/p1", + text: list.join(", "), + }); + await test_booleanList({ + core, + name: "/bl2", + booleans: list.slice(0, 3), + pName: "/p2", + text: list.slice(0, 3).join(", "), + }); + await test_booleanList({ + core, + name: "/bl3", + booleans: list, + pName: "/p3", + text: list.join(", "), + }); + await test_booleanList({ + core, + name: "/bl4", + booleans: list.slice(0, 3), + pName: "/p4", + text: list.slice(0, 3).join(", "), + }); + await test_booleanList({ + core, + name: "/bl5", + booleans: list.slice(0, 4), + pName: "/p5", + text: list.slice(0, 4).join(", "), + }); + await test_booleanList({ + core, + name: "/bl6", + booleans: list, + pName: "/p6", + text: list.join(", "), + }); + }); + + it("dynamic maximum number", async () => { + let core = await createTestCore({ + doenetML: ` + +

Maximum number 1:

+

Maximum number 2:

+
+

true true false true false false

+

$bl1{maxNumber="$mn2" name="bl2"}

+

$bl2{name="bl3"}

+

$bl3{name="bl4" maxNumber=""}

+
+
+ + `, + }); + + let list = [true, true, false, true, false, false]; + + async function check_items(max1, max2) { + for (let pre of ["", "/sec2"]) { + await test_booleanList({ + core, + name: `${pre}/bl1`, + booleans: list.slice(0, max1), + pName: `${pre}/p1`, + text: list.slice(0, max1).join(", "), + }); + await test_booleanList({ + core, + name: `${pre}/bl2`, + booleans: list.slice(0, max2), + pName: `${pre}/p2`, + text: list.slice(0, max2).join(", "), + }); + await test_booleanList({ + core, + name: `${pre}/bl3`, + booleans: list.slice(0, max2), + pName: `${pre}/p3`, + text: list.slice(0, max2).join(", "), + }); + await test_booleanList({ + core, + name: `${pre}/bl4`, + booleans: list, + pName: `${pre}/p4`, + text: list.join(", "), + }); + } + } + + let max1 = 2, + max2 = Infinity; + + await check_items(max1, max2); + + max1 = Infinity; + await updateMathInputValue({ latex: "", componentName: "/mn1", core }); + await check_items(max1, max2); + + max2 = 3; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + + max1 = 4; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max1 = 1; + await updateMathInputValue({ + latex: max1.toString(), + componentName: "/mn1", + core, + }); + await check_items(max1, max2); + + max2 = 10; + await updateMathInputValue({ + latex: max2.toString(), + componentName: "/mn2", + core, + }); + await check_items(max1, max2); + }); + + it("booleanList within booleanLists, ignore child hide", async () => { + let core = await createTestCore({ + doenetML: ` +

true true false

+ +

+ false + $bl1 + true + $bl1{hide="false"} +

+ +

$bl2{name="bl3" maxNumber="6"}

+ + `, + }); + + await test_booleanList({ + core, + name: "/bl2", + booleans: [false, true, true, false, true, true, true, false], + pName: "/p2", + text: "false, true, true, false, true, true, true, false", + }); + + await test_booleanList({ + core, + name: "/bl3", + booleans: [false, true, true, false, true, true], + pName: "/p3", + text: "false, true, true, false, true, true", + }); + }); + + it("booleanList does not force composite replacement, even in boolean", async () => { + let core = await createTestCore({ + doenetML: ` + + $nothing true = true + + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/b"].stateValues.value).eq(true); + }); + + it("assignNames", async () => { + let core = await createTestCore({ + doenetML: ` +

true true fall

+

$a, $b, $c

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + + expect(stateVariables["/p1"].stateValues.text).eq("true, true, false"); + expect(stateVariables["/p2"].stateValues.text).eq("true, true, false"); + expect(stateVariables["/a"].stateValues.value).eq(true); + expect(stateVariables["/b"].stateValues.value).eq(true); + expect(stateVariables["/c"].stateValues.value).eq(false); + }); + + it("text from booleanList", async () => { + let core = await createTestCore({ + doenetML: ` + true true false + +

Text: $bl.text

+ + `, + }); + + const stateVariables = await returnAllStateVariables(core); + expect(stateVariables["/pText"].stateValues.text).eq( + "Text: true, true, false", + ); + }); +}); diff --git a/packages/test-cypress/cypress/e2e/tagSpecific/booleanlist.cy.js b/packages/test-cypress/cypress/e2e/tagSpecific/booleanlist.cy.js deleted file mode 100644 index 6f2c94c0c..000000000 --- a/packages/test-cypress/cypress/e2e/tagSpecific/booleanlist.cy.js +++ /dev/null @@ -1,686 +0,0 @@ -import { cesc } from "@doenet/utils"; - -describe("BooleanList Tag Tests", function () { - beforeEach(() => { - cy.clearIndexedDB(); - cy.visit("/"); - }); - - it("booleanlist within booleanlists", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` -

false true false

- -

$_booleanlist1{hide="false" name="bl1a"}

- -

- true - false - $_booleanlist1{hide="false"} - false - $bl1a -

- -

- `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_p1")).should("have.text", ""); - cy.get(cesc("#\\/_p2")).should("have.text", "false, true, false"); - cy.get(cesc("#\\/_p3")).should( - "have.text", - "true, false, true, false, false, false, true, false", - ); - cy.get(cesc("#\\/_p4")).should( - "have.text", - "true, false, true, false, false", - ); - }); - - it("booleanlist with booleanlist children, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- true - false false - false - - - false - true false - - false true - -

- - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "true, false, false, false, false, true, false, false, true", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[3]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[4]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[5]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[6]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[7]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[8]).eq( - true, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[3]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[4]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[1]).eq( - true, - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_booleaninput1")).click(); - cy.get(cesc("#\\/_booleaninput2")).click(); - cy.get(cesc("#\\/_booleaninput3")).click(); - cy.get(cesc("#\\/_booleaninput4")).click(); - cy.get(cesc("#\\/_booleaninput5")).click(); - cy.get(cesc("#\\/_booleaninput6")).click(); - cy.get(cesc("#\\/_booleaninput7")).click(); - cy.get(cesc("#\\/_booleaninput8")).click(); - cy.get(cesc("#\\/_booleaninput9")).click(); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "false, true, true, true, true, false, true, true, false", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[3]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[4]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[5]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[6]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[7]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[8]).eq( - false, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[3]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[4]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[1]).eq( - false, - ); - }); - }); - - it("booleanlist with booleanlist children and sugar, test inverse", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

- true - false false - false - - - false - true false - - false true - -

- - - - - - - - - - - - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "true, false, false, false, false, true, false, false, true", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[3]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[4]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[5]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[6]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[7]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[8]).eq( - true, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[3]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[4]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[2]).eq( - false, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[1]).eq( - true, - ); - }); - - cy.log("change values"); - - cy.get(cesc("#\\/_booleaninput1")).click(); - cy.get(cesc("#\\/_booleaninput2")).click(); - cy.get(cesc("#\\/_booleaninput3")).click(); - cy.get(cesc("#\\/_booleaninput4")).click(); - cy.get(cesc("#\\/_booleaninput5")).click(); - cy.get(cesc("#\\/_booleaninput6")).click(); - cy.get(cesc("#\\/_booleaninput7")).click(); - cy.get(cesc("#\\/_booleaninput8")).click(); - cy.get(cesc("#\\/_booleaninput9")).click(); - - cy.log("Test value displayed in browser"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "false, true, true, true, true, false, true, true, false", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[3]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[4]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[5]).eq( - false, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[6]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[7]).eq( - true, - ); - expect(stateVariables["/_booleanlist1"].stateValues.booleans[8]).eq( - false, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist2"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[3]).eq( - true, - ); - expect(stateVariables["/_booleanlist3"].stateValues.booleans[4]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[1]).eq( - false, - ); - expect(stateVariables["/_booleanlist4"].stateValues.booleans[2]).eq( - true, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[0]).eq( - false, - ); - expect(stateVariables["/_booleanlist5"].stateValues.booleans[1]).eq( - true, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[0]).eq( - true, - ); - expect(stateVariables["/_booleanlist6"].stateValues.booleans[1]).eq( - false, - ); - }); - }); - - it("copy booleanlist and overwrite maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

true true false true false

-

$bl1{maxNumber="3" name="bl2"}

-

$bl2{maxNumber="" name="bl3"}

- -

true true false true false

-

$bl4{maxNumber="4" name="bl5"}

-

$bl5{maxNumber="" name="bl6"}

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")).should( - "have.text", - "true, true, false, true, false", - ); - cy.get(cesc("#\\/_p2")).should("have.text", "true, true, false"); - cy.get(cesc("#\\/_p3")).should( - "have.text", - "true, true, false, true, false", - ); - - cy.get(cesc("#\\/_p4")).should("have.text", "true, true, false"); - cy.get(cesc("#\\/_p5")).should( - "have.text", - "true, true, false, true", - ); - cy.get(cesc("#\\/_p6")).should( - "have.text", - "true, true, false, true, false", - ); - - cy.log("Test internal values are set to the correct values"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - ]); - expect(stateVariables["/bl3"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - expect(stateVariables["/bl4"].stateValues.booleans).eqls([ - true, - true, - false, - ]); - expect(stateVariables["/bl5"].stateValues.booleans).eqls([ - true, - true, - false, - true, - ]); - expect(stateVariables["/bl6"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - }); - }); - }); - - it("dynamic maximum number", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

true true false true false

-

-

Maximum number 1:

-

Maximum number 2:

- - `, - }, - "*", - ); - }); - - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait until loaded - - cy.window().then(async (win) => { - cy.get(cesc("#\\/_p1")).should("have.text", "true, true"); - cy.get(cesc("#\\/_p2")).should( - "have.text", - "true, true, false, true, false", - ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([ - true, - true, - ]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - }); - }); - - cy.log("clear first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea") - .type("{end}{backspace}", { force: true }) - .blur(); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "true, true, false, true, false", - ); - cy.get(cesc("#\\/_p2")).should( - "have.text", - "true, true, false, true, false", - ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - }); - - cy.log("number in second maxnum"); - cy.get(cesc("#\\/mn2") + " textarea").type("3{enter}", { force: true }); - cy.get(cesc("#\\/_p2")).should("have.text", "true, true, false"); - cy.get(cesc("#\\/_p1")).should( - "have.text", - "true, true, false, true, false", - ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([ - true, - true, - false, - true, - false, - ]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - ]); - }); - - cy.log("number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("4{enter}", { force: true }); - cy.get(cesc("#\\/_p1")).should("have.text", "true, true, false, true"); - cy.get(cesc("#\\/_p2")).should("have.text", "true, true, false"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([ - true, - true, - false, - true, - ]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - ]); - }); - - cy.log("change number in first maxnum"); - cy.get(cesc("#\\/mn1") + " textarea").type("{end}{backspace}1{enter}", { - force: true, - }); - cy.get(cesc("#\\/_p1")).should("have.text", "true"); - cy.get(cesc("#\\/_p2")).should("have.text", "true, true, false"); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/bl1"].stateValues.booleans).eqls([true]); - expect(stateVariables["/bl2"].stateValues.booleans).eqls([ - true, - true, - false, - ]); - }); - }); -}); From cf16c4cae9b3775b2f966d4f01ff68aff93506a7 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 08:27:20 -0500 Subject: [PATCH 08/13] bump version --- package-lock.json | 6 +++--- packages/doenetml-iframe/package.json | 2 +- packages/doenetml/package.json | 2 +- packages/standalone/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02c69e597..865628710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22819,7 +22819,7 @@ }, "packages/doenetml": { "name": "@doenet/doenetml", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "dependencies": { "@chakra-ui/icons": "^2.0.19", @@ -22861,7 +22861,7 @@ }, "packages/doenetml-iframe": { "name": "@doenet/doenetml-iframe", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "devDependencies": {}, "peerDependencies": { @@ -22939,7 +22939,7 @@ }, "packages/standalone": { "name": "@doenet/standalone", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "devDependencies": {} }, diff --git a/packages/doenetml-iframe/package.json b/packages/doenetml-iframe/package.json index 789d40702..36cdd381d 100644 --- a/packages/doenetml-iframe/package.json +++ b/packages/doenetml-iframe/package.json @@ -2,7 +2,7 @@ "name": "@doenet/doenetml-iframe", "type": "module", "description": "A renderer for DoenetML contained in an iframe", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "homepage": "https://github.com/Doenet/DoenetML#readme", "private": true, diff --git a/packages/doenetml/package.json b/packages/doenetml/package.json index 6c6044a65..0a2c6dfb1 100644 --- a/packages/doenetml/package.json +++ b/packages/doenetml/package.json @@ -2,7 +2,7 @@ "name": "@doenet/doenetml", "type": "module", "description": "Semantic markup for building interactive web activities", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "homepage": "https://github.com/Doenet/DoenetML#readme", "private": true, diff --git a/packages/standalone/package.json b/packages/standalone/package.json index 94a1ff434..52b877a33 100644 --- a/packages/standalone/package.json +++ b/packages/standalone/package.json @@ -2,7 +2,7 @@ "name": "@doenet/standalone", "type": "module", "description": "Standalone renderer for DoenetML suitable for being included in a web page", - "version": "0.7.0-alpha20", + "version": "0.7.0-alpha21", "license": "AGPL-3.0-or-later", "homepage": "https://github.com/Doenet/DoenetML#readme", "private": true, From 99dfb3d8a365821c631be65bf5e8f35dc3ffb4e4 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 08:29:15 -0500 Subject: [PATCH 09/13] build schema --- .../src/generated/doenet-relaxng-schema.json | 2226 +++++------------ .../src/generated/doenet-schema.json | 1626 ++++++------ 2 files changed, 1261 insertions(+), 2591 deletions(-) diff --git a/packages/static-assets/src/generated/doenet-relaxng-schema.json b/packages/static-assets/src/generated/doenet-relaxng-schema.json index 8fed98abe..feee365ba 100644 --- a/packages/static-assets/src/generated/doenet-relaxng-schema.json +++ b/packages/static-assets/src/generated/doenet-relaxng-schema.json @@ -487,27 +487,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -992,15 +977,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -1633,27 +1609,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -2971,27 +2932,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -3575,27 +3521,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -6345,24 +6276,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -6595,11 +6514,6 @@ "type": "topic", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -6614,6 +6528,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ] }, @@ -6910,27 +6878,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -7500,27 +7453,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -8090,27 +8028,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -9041,27 +8964,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -9544,15 +9452,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -9670,9 +9569,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -9691,9 +9587,6 @@ { "ref": "booleanInput" }, - { - "ref": "booleanList" - }, { "ref": "orbitalDiagram" } @@ -10792,15 +10685,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -11222,15 +11106,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -11660,15 +11535,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -12150,15 +12016,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -12773,15 +12630,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -13396,15 +13244,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -14031,15 +13870,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -14666,15 +14496,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -15297,15 +15118,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -15915,15 +15727,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -16527,15 +16330,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -17144,15 +16938,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -17761,15 +17546,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -18378,15 +18154,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -19003,15 +18770,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -19626,15 +19384,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -20253,15 +20002,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -20885,15 +20625,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -21513,15 +21244,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -22136,15 +21858,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -22759,15 +22472,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -23382,15 +23086,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -24005,15 +23700,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -24637,15 +24323,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -25287,15 +24964,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -26011,15 +25679,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -26735,15 +26394,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -27384,15 +27034,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -27586,11 +27227,6 @@ "type": "text", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -27605,6 +27241,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ] }, @@ -27875,27 +27565,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -28399,27 +28074,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -28923,27 +28583,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -29447,27 +29092,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -29971,27 +29601,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -30495,27 +30110,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -31019,27 +30619,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -31543,27 +31128,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -32067,27 +31637,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -32591,27 +32146,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -50100,6 +49640,11 @@ "name": "doenetML", "type": "text", "isArray": false + }, + { + "name": "text", + "type": "text", + "isArray": false } ] }, @@ -51186,15 +50731,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "line" }, @@ -51664,15 +51200,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -52259,15 +51786,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -53548,15 +53066,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -54085,12 +53594,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -56392,24 +55895,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -56642,11 +56133,6 @@ "type": "h", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -56661,6 +56147,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ] }, @@ -56820,24 +56360,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -61481,27 +61009,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -62859,24 +62372,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -63109,11 +62610,6 @@ "type": "text", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -63128,6 +62624,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ] }, @@ -63143,6 +62693,10 @@ "optional": true, "type": ["string"] }, + "assignNames": { + "optional": true, + "type": ["string"] + }, "hide": { "optional": true, "type": ["true", "false"] @@ -63153,7 +62707,7 @@ }, "fixed": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "fixLocation": { "optional": true, @@ -63165,7 +62719,7 @@ }, "isResponse": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "newNamespace": { "optional": true, @@ -63295,24 +62849,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -63406,11 +62948,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -63461,24 +62998,6 @@ "type": "number", "isArray": false }, - { - "name": "texts", - "type": "text", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "text", - "numDimensions": 1 - } - ] - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -63486,16 +63005,8 @@ }, { "name": "values", - "type": "text", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "text", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ] }, @@ -63766,27 +63277,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -64230,15 +63726,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -64356,9 +63843,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -64377,9 +63861,6 @@ { "ref": "booleanInput" }, - { - "ref": "booleanList" - }, { "ref": "orbitalDiagram" } @@ -64525,6 +64006,10 @@ "optional": true, "type": ["string"] }, + "assignNames": { + "optional": true, + "type": ["string"] + }, "hide": { "optional": true, "type": ["true", "false"] @@ -64535,7 +64020,7 @@ }, "fixed": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "fixLocation": { "optional": true, @@ -64547,7 +64032,7 @@ }, "isResponse": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "newNamespace": { "optional": true, @@ -64595,219 +64080,6 @@ }, { "ref": "matchesPattern" - }, - { - "ref": "booleanList" - }, - { - "ref": "rightHandSide" - }, - { - "ref": "topic" - }, - { - "ref": "sum" - }, - { - "ref": "product" - }, - { - "ref": "clampNumber" - }, - { - "ref": "wrapNumberPeriodic" - }, - { - "ref": "round" - }, - { - "ref": "setSmallToZero" - }, - { - "ref": "convertSetToList" - }, - { - "ref": "ceil" - }, - { - "ref": "floor" - }, - { - "ref": "abs" - }, - { - "ref": "sign" - }, - { - "ref": "mean" - }, - { - "ref": "median" - }, - { - "ref": "variance" - }, - { - "ref": "standardDeviation" - }, - { - "ref": "count" - }, - { - "ref": "min" - }, - { - "ref": "max" - }, - { - "ref": "mod" - }, - { - "ref": "gcd" - }, - { - "ref": "extractMath" - }, - { - "ref": "clampFunction" - }, - { - "ref": "wrapFunctionPeriodic" - }, - { - "ref": "derivative" - }, - { - "ref": "extractMathOperator" - }, - { - "ref": "equilibriumPoint" - }, - { - "ref": "equilibriumLine" - }, - { - "ref": "atom" - }, - { - "ref": "ion" - }, - { - "ref": "ionicCompound" - }, - { - "ref": "electronConfiguration" - }, - { - "ref": "h" - }, - { - "ref": "matrixInput" - }, - { - "ref": "text" - }, - { - "ref": "math" - }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, - { - "ref": "point" - }, - { - "ref": "coords" - }, - { - "ref": "line" - }, - { - "ref": "vector" - }, - { - "ref": "angle" - }, - { - "ref": "mathInput" - }, - { - "ref": "choice" - }, - { - "ref": "number" - }, - { - "ref": "integer" - }, - { - "ref": "function" - }, - { - "ref": "piecewiseFunction" - }, - { - "ref": "interval" - }, - { - "ref": "sequence" - }, - { - "ref": "cell" - }, - { - "ref": "selectFromSequence" - }, - { - "ref": "evaluate" - }, - { - "ref": "selectRandomNumbers" - }, - { - "ref": "sampleRandomNumbers" - }, - { - "ref": "selectPrimeNumbers" - }, - { - "ref": "samplePrimeNumbers" - }, - { - "ref": "substitute" - }, - { - "ref": "periodicSet" - }, - { - "ref": "intcomma" - }, - { - "ref": "pluralize" - }, - { - "ref": "lorem" - }, - { - "ref": "endpoint" - }, - { - "ref": "subsetOfReals" - }, - { - "ref": "bestFitLine" - }, - { - "ref": "matrix" - }, - { - "ref": "latex" } ], "textChildrenAllowed": true, @@ -64827,11 +64099,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -64882,19 +64149,6 @@ "type": "number", "isArray": false }, - { - "name": "booleans", - "type": "boolean", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "boolean", - "numDimensions": 1 - } - ] - }, { "name": "numValues", "type": "number", @@ -64902,16 +64156,8 @@ }, { "name": "values", - "type": "boolean", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "boolean", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ] }, @@ -65158,15 +64404,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -65544,6 +64781,10 @@ "optional": true, "type": ["string"] }, + "assignNames": { + "optional": true, + "type": ["string"] + }, "hide": { "optional": true, "type": ["true", "false"] @@ -65554,7 +64795,7 @@ }, "fixed": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "fixLocation": { "optional": true, @@ -65566,7 +64807,7 @@ }, "isResponse": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "newNamespace": { "optional": true, @@ -65598,7 +64839,7 @@ }, "padZeros": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "functionSymbols": { "optional": true, @@ -65729,15 +64970,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -65846,11 +65078,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -65911,26 +65138,6 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "mergeMathLists", "type": "boolean", @@ -65954,34 +65161,6 @@ } ] }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "latex", - "type": "latex", - "isArray": false - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -66014,6 +65193,10 @@ "optional": true, "type": ["string"] }, + "assignNames": { + "optional": true, + "type": ["string"] + }, "hide": { "optional": true, "type": ["true", "false"] @@ -66024,7 +65207,7 @@ }, "fixed": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "fixLocation": { "optional": true, @@ -66036,7 +65219,7 @@ }, "isResponse": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "newNamespace": { "optional": true, @@ -66068,7 +65251,7 @@ }, "padZeros": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "functionSymbols": { "optional": true, @@ -66199,15 +65382,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -66316,11 +65490,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -66381,26 +65550,6 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "mergeMathLists", "type": "boolean", @@ -66424,34 +65573,6 @@ } ] }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "latex", - "type": "latex", - "isArray": false - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -66484,6 +65605,10 @@ "optional": true, "type": ["string"] }, + "assignNames": { + "optional": true, + "type": ["string"] + }, "hide": { "optional": true, "type": ["true", "false"] @@ -66494,7 +65619,7 @@ }, "fixed": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "fixLocation": { "optional": true, @@ -66506,7 +65631,7 @@ }, "isResponse": { "optional": true, - "type": ["true", "false"] + "type": ["string"] }, "newNamespace": { "optional": true, @@ -66534,7 +65659,7 @@ }, "padZeros": { "optional": true, - "type": ["true", "false"] + "type": ["string"] } }, "children": [ @@ -66681,15 +65806,6 @@ }, { "ref": "latex" - }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" } ], "textChildrenAllowed": true, @@ -66709,11 +65825,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -66759,67 +65870,11 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "numComponents", "type": "number", "isArray": false }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "maths", - "type": "math", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "math", - "numDimensions": 1 - } - ] - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -66827,16 +65882,8 @@ }, { "name": "values", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ] }, @@ -68067,15 +67114,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "line" }, @@ -68539,15 +67577,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -69140,15 +68169,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -72037,15 +71057,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -72626,15 +71637,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -73217,15 +72219,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -73826,15 +72819,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -74181,15 +73165,6 @@ { "ref": "text" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -74627,15 +73602,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "line" }, @@ -75155,15 +74121,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -75629,15 +74586,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -75725,6 +74673,15 @@ { "ref": "latex" }, + { + "ref": "mathList" + }, + { + "ref": "tupleList" + }, + { + "ref": "numberList" + }, { "ref": "xlabel" }, @@ -75752,9 +74709,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -75770,6 +74724,9 @@ { "ref": "matchesPattern" }, + { + "ref": "textList" + }, { "ref": "booleanList" } @@ -76298,15 +75255,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -76424,9 +75372,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -76442,9 +75387,6 @@ { "ref": "booleanInput" }, - { - "ref": "booleanList" - }, { "ref": "orbitalDiagram" } @@ -76851,15 +75793,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -76977,9 +75910,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -76998,9 +75928,6 @@ { "ref": "booleanInput" }, - { - "ref": "booleanList" - }, { "ref": "orbitalDiagram" } @@ -77375,15 +76302,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -77641,6 +76559,69 @@ "name": "rawRendererValue", "type": "latex", "isArray": false + }, + { + "name": "numDimensions", + "type": "integer", + "isArray": false + }, + { + "name": "vector", + "type": "math", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": false, + "type": "vector" + } + ] + }, + { + "name": "matrixSize", + "type": "numberList", + "isArray": false + }, + { + "name": "numRows", + "type": "integer", + "isArray": false + }, + { + "name": "numColumns", + "type": "integer", + "isArray": false + }, + { + "name": "matrix", + "type": "math", + "isArray": true, + "numDimensions": 2, + "indexedArrayDescription": [ + { + "isArray": false, + "type": "matrix" + }, + { + "isArray": false, + "type": "matrix" + } + ] + }, + { + "name": "x", + "type": "math", + "isArray": false + }, + { + "name": "y", + "type": "math", + "isArray": false + }, + { + "name": "z", + "type": "math", + "isArray": false } ] }, @@ -77849,24 +76830,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -78071,6 +77040,60 @@ "name": "text", "type": "text", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ] }, @@ -78307,15 +77330,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -79923,15 +78937,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -79989,9 +78994,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -80417,15 +79419,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -80483,9 +79476,6 @@ { "ref": "isBetween" }, - { - "ref": "textList" - }, { "ref": "boolean" }, @@ -81076,21 +80066,9 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "when" }, @@ -81661,15 +80639,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -82822,15 +81791,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -85204,21 +84164,9 @@ { "ref": "ion" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "when" }, @@ -85828,15 +84776,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -86161,6 +85100,15 @@ { "ref": "booleanList" }, + { + "ref": "mathList" + }, + { + "ref": "tupleList" + }, + { + "ref": "numberList" + }, { "ref": "collect" }, @@ -87378,27 +86326,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -88070,27 +87003,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -88637,24 +87555,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -92932,27 +91838,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -95151,10 +94042,6 @@ "optional": true, "type": ["string"] }, - "variantDeterminesSeed": { - "optional": true, - "type": ["true", "false"] - }, "asList": { "optional": true, "type": ["true", "false"] @@ -95202,11 +94089,6 @@ "type": "text", "isArray": false }, - { - "name": "variantDeterminesSeed", - "type": "boolean", - "isArray": false - }, { "name": "numToSelect", "type": "integer", @@ -98507,24 +97389,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -98752,11 +97622,6 @@ "type": "point", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -98772,6 +97637,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "originalValue", "type": "intcomma", @@ -98978,24 +97897,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -99233,11 +98140,6 @@ "type": "point", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -99253,6 +98155,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "valuePrePluralize", "type": "pluralize", @@ -104452,27 +103408,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -104981,27 +103922,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -105700,15 +104626,6 @@ { "ref": "matrixInput" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "line" }, @@ -107684,15 +106601,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -108378,15 +107286,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -111649,15 +110548,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -112725,27 +111615,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, - { - "ref": "booleanList" - }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "collect" }, @@ -113261,15 +112136,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -114120,15 +112986,6 @@ { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "point" }, @@ -114515,24 +113372,12 @@ { "ref": "text" }, - { - "ref": "textList" - }, { "ref": "boolean" }, { "ref": "math" }, - { - "ref": "mathList" - }, - { - "ref": "tupleList" - }, - { - "ref": "numberList" - }, { "ref": "coords" }, @@ -114831,11 +113676,6 @@ "type": "latex", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -114851,6 +113691,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "latex", "type": "latex", diff --git a/packages/static-assets/src/generated/doenet-schema.json b/packages/static-assets/src/generated/doenet-schema.json index 2b824e96a..b06088004 100644 --- a/packages/static-assets/src/generated/doenet-schema.json +++ b/packages/static-assets/src/generated/doenet-schema.json @@ -78,13 +78,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -288,9 +283,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -789,13 +781,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -1371,13 +1358,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -1691,13 +1673,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -2928,12 +2905,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -3142,11 +3115,6 @@ "type": "topic", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -3161,6 +3129,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ], "top": true, @@ -3244,13 +3266,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -3551,13 +3568,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -3858,13 +3870,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -4504,13 +4511,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -4782,9 +4784,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -4824,14 +4823,12 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", "label", "matchesPattern", "booleanInput", - "booleanList", "orbitalDiagram" ], "attributes": [ @@ -5784,9 +5781,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -6073,9 +6067,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -6362,9 +6353,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -6668,9 +6656,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -7145,9 +7130,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -7622,9 +7604,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -8101,9 +8080,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -8580,9 +8556,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -9059,9 +9032,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -9530,9 +9500,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -9984,9 +9951,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -10447,9 +10411,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -10910,9 +10871,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -11373,9 +11331,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -11836,9 +11791,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -12313,9 +12265,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -12790,9 +12739,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -13276,9 +13222,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -13762,9 +13705,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -14239,9 +14179,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -14716,9 +14653,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -15193,9 +15127,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -15670,9 +15601,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -16147,9 +16075,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -16635,9 +16560,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -17197,9 +17119,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -17759,9 +17678,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -18329,9 +18245,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -18527,11 +18440,6 @@ "type": "text", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -18546,6 +18454,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ], "top": true, @@ -18629,13 +18591,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -18872,13 +18829,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -19115,13 +19067,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -19358,13 +19305,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -19601,13 +19543,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -19844,13 +19781,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -20087,13 +20019,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -20330,13 +20257,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -20573,13 +20495,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -20816,13 +20733,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -30159,6 +30071,11 @@ "name": "doenetML", "type": "text", "isArray": false + }, + { + "name": "text", + "type": "text", + "isArray": false } ], "top": false, @@ -30952,9 +30869,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "line", "angle", "mathInput", @@ -31326,9 +31240,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -31724,9 +31635,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -32848,9 +32756,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -33313,8 +33218,6 @@ "matrixInput", "text", "math", - "mathList", - "numberList", "point", "coords", "line", @@ -34806,12 +34709,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -35023,11 +34922,6 @@ "type": "h", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -35042,197 +34936,247 @@ "name": "number", "type": "number", "isArray": false - } - ], - "top": true, - "acceptsStringChildren": true - }, - { - "name": "idx", - "children": [ - "h", - "rightHandSide", - "xlabel", - "ylabel", - "topic", - "not", - "and", - "or", - "xor", - "isInteger", - "isNumber", - "isBetween", - "sum", - "product", - "clampNumber", - "wrapNumberPeriodic", - "round", - "setSmallToZero", - "convertSetToList", - "ceil", - "floor", - "abs", - "sign", - "mean", - "median", - "variance", - "standardDeviation", - "count", - "min", - "max", - "mod", - "gcd", - "extractMath", - "extractMathOperator", - "atom", - "ion", - "electronConfiguration", - "text", - "textList", - "boolean", - "math", - "mathList", - "tupleList", - "numberList", - "coords", - "when", - "choice", - "number", - "integer", - "interval", - "sequence", - "cell", - "selectFromSequence", - "evaluate", - "selectRandomNumbers", - "sampleRandomNumbers", - "selectPrimeNumbers", - "samplePrimeNumbers", - "substitute", - "periodicSet", - "intcomma", - "pluralize", - "lorem", - "subsetOfReals", - "hasSameFactoring", - "label", - "matchesPattern", - "matrix", - "latex" - ], - "attributes": [ - { - "name": "name" - }, - { - "name": "copySource" - }, - { - "name": "hide", - "values": ["true", "false"] - }, - { - "name": "disabled", - "values": ["true", "false"] - }, - { - "name": "fixed", - "values": ["true", "false"] - }, - { - "name": "fixLocation", - "values": ["true", "false"] - }, - { - "name": "styleNumber" - }, - { - "name": "isResponse", - "values": ["true", "false"] - }, - { - "name": "newNamespace", - "values": ["true", "false"] - } - ], - "properties": [ - { - "name": "hide", - "type": "boolean", - "isArray": false - }, - { - "name": "modifyIndirectly", - "type": "boolean", - "isArray": false }, { - "name": "styleNumber", + "name": "numWords", "type": "integer", "isArray": false }, { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, - { - "name": "newNamespace", - "type": "boolean", - "isArray": false - }, - { - "name": "permid", + "name": "words", "type": "text", - "isArray": false - }, - { - "name": "hidden", - "type": "boolean", - "isArray": false + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] }, { - "name": "disabled", - "type": "boolean", + "name": "numCharacters", + "type": "integer", "isArray": false }, { - "name": "fixed", - "type": "boolean", - "isArray": false + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] }, { - "name": "fixLocation", - "type": "boolean", + "name": "numListItems", + "type": "integer", "isArray": false }, { - "name": "doenetML", + "name": "listItems", "type": "text", - "isArray": false + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ], "top": true, "acceptsStringChildren": true }, { - "name": "div", + "name": "idx", "children": [ - "title", + "h", "rightHandSide", - "description", "xlabel", "ylabel", - "statement", - "introduction", - "conclusion", "topic", - "m", - "me", - "men", - "md", - "mdn", - "mrow", + "not", + "and", + "or", + "xor", + "isInteger", + "isNumber", + "isBetween", + "sum", + "product", + "clampNumber", + "wrapNumberPeriodic", + "round", + "setSmallToZero", + "convertSetToList", + "ceil", + "floor", + "abs", + "sign", + "mean", + "median", + "variance", + "standardDeviation", + "count", + "min", + "max", + "mod", + "gcd", + "extractMath", + "extractMathOperator", + "atom", + "ion", + "electronConfiguration", + "text", + "boolean", + "math", + "coords", + "when", + "choice", + "number", + "integer", + "interval", + "sequence", + "cell", + "selectFromSequence", + "evaluate", + "selectRandomNumbers", + "sampleRandomNumbers", + "selectPrimeNumbers", + "samplePrimeNumbers", + "substitute", + "periodicSet", + "intcomma", + "pluralize", + "lorem", + "subsetOfReals", + "hasSameFactoring", + "label", + "matchesPattern", + "matrix", + "latex" + ], + "attributes": [ + { + "name": "name" + }, + { + "name": "copySource" + }, + { + "name": "hide", + "values": ["true", "false"] + }, + { + "name": "disabled", + "values": ["true", "false"] + }, + { + "name": "fixed", + "values": ["true", "false"] + }, + { + "name": "fixLocation", + "values": ["true", "false"] + }, + { + "name": "styleNumber" + }, + { + "name": "isResponse", + "values": ["true", "false"] + }, + { + "name": "newNamespace", + "values": ["true", "false"] + } + ], + "properties": [ + { + "name": "hide", + "type": "boolean", + "isArray": false + }, + { + "name": "modifyIndirectly", + "type": "boolean", + "isArray": false + }, + { + "name": "styleNumber", + "type": "integer", + "isArray": false + }, + { + "name": "isResponse", + "type": "boolean", + "isArray": false + }, + { + "name": "newNamespace", + "type": "boolean", + "isArray": false + }, + { + "name": "permid", + "type": "text", + "isArray": false + }, + { + "name": "hidden", + "type": "boolean", + "isArray": false + }, + { + "name": "disabled", + "type": "boolean", + "isArray": false + }, + { + "name": "fixed", + "type": "boolean", + "isArray": false + }, + { + "name": "fixLocation", + "type": "boolean", + "isArray": false + }, + { + "name": "doenetML", + "type": "text", + "isArray": false + } + ], + "top": true, + "acceptsStringChildren": true + }, + { + "name": "div", + "children": [ + "title", + "rightHandSide", + "description", + "xlabel", + "ylabel", + "statement", + "introduction", + "conclusion", + "topic", + "m", + "me", + "men", + "md", + "mdn", + "mrow", "not", "and", "or", @@ -37304,13 +37248,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -37902,12 +37841,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -38116,11 +38051,6 @@ "type": "text", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -38135,6 +38065,60 @@ "name": "number", "type": "number", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ], "top": true, @@ -38181,12 +38165,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -38220,6 +38200,9 @@ { "name": "copySource" }, + { + "name": "assignNames" + }, { "name": "hide", "values": ["true", "false"] @@ -38229,8 +38212,7 @@ "values": ["true", "false"] }, { - "name": "fixed", - "values": ["true", "false"] + "name": "fixed" }, { "name": "fixLocation", @@ -38240,8 +38222,7 @@ "name": "styleNumber" }, { - "name": "isResponse", - "values": ["true", "false"] + "name": "isResponse" }, { "name": "newNamespace", @@ -38271,11 +38252,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -38326,24 +38302,6 @@ "type": "number", "isArray": false }, - { - "name": "texts", - "type": "text", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "text", - "numDimensions": 1 - } - ] - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -38351,16 +38309,8 @@ }, { "name": "values", - "type": "text", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "text", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ], "top": true, @@ -38444,13 +38394,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -38651,9 +38596,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -38693,14 +38635,12 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", "label", "matchesPattern", "booleanInput", - "booleanList", "orbitalDiagram" ], "attributes": [ @@ -38931,78 +38871,7 @@ "boolean", "booleanInput", "hasSameFactoring", - "matchesPattern", - "booleanList", - "rightHandSide", - "topic", - "sum", - "product", - "clampNumber", - "wrapNumberPeriodic", - "round", - "setSmallToZero", - "convertSetToList", - "ceil", - "floor", - "abs", - "sign", - "mean", - "median", - "variance", - "standardDeviation", - "count", - "min", - "max", - "mod", - "gcd", - "extractMath", - "clampFunction", - "wrapFunctionPeriodic", - "derivative", - "extractMathOperator", - "equilibriumPoint", - "equilibriumLine", - "atom", - "ion", - "ionicCompound", - "electronConfiguration", - "h", - "matrixInput", - "text", - "math", - "mathList", - "tupleList", - "numberList", - "point", - "coords", - "line", - "vector", - "angle", - "mathInput", - "choice", - "number", - "integer", - "function", - "piecewiseFunction", - "interval", - "sequence", - "cell", - "selectFromSequence", - "evaluate", - "selectRandomNumbers", - "sampleRandomNumbers", - "selectPrimeNumbers", - "samplePrimeNumbers", - "substitute", - "periodicSet", - "intcomma", - "pluralize", - "lorem", - "endpoint", - "subsetOfReals", - "bestFitLine", - "matrix", - "latex" + "matchesPattern" ], "attributes": [ { @@ -39011,6 +38880,9 @@ { "name": "copySource" }, + { + "name": "assignNames" + }, { "name": "hide", "values": ["true", "false"] @@ -39020,8 +38892,7 @@ "values": ["true", "false"] }, { - "name": "fixed", - "values": ["true", "false"] + "name": "fixed" }, { "name": "fixLocation", @@ -39031,8 +38902,7 @@ "name": "styleNumber" }, { - "name": "isResponse", - "values": ["true", "false"] + "name": "isResponse" }, { "name": "newNamespace", @@ -39062,11 +38932,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -39117,19 +38982,6 @@ "type": "number", "isArray": false }, - { - "name": "booleans", - "type": "boolean", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "boolean", - "numDimensions": 1 - } - ] - }, { "name": "numValues", "type": "number", @@ -39137,16 +38989,8 @@ }, { "name": "values", - "type": "boolean", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "boolean", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ], "top": true, @@ -39192,9 +39036,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -39655,9 +39496,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -39696,6 +39534,9 @@ { "name": "copySource" }, + { + "name": "assignNames" + }, { "name": "hide", "values": ["true", "false"] @@ -39705,8 +39546,7 @@ "values": ["true", "false"] }, { - "name": "fixed", - "values": ["true", "false"] + "name": "fixed" }, { "name": "fixLocation", @@ -39716,8 +39556,7 @@ "name": "styleNumber" }, { - "name": "isResponse", - "values": ["true", "false"] + "name": "isResponse" }, { "name": "newNamespace", @@ -39744,8 +39583,7 @@ "name": "displaySmallAsZero" }, { - "name": "padZeros", - "values": ["true", "false"] + "name": "padZeros" }, { "name": "functionSymbols" @@ -39778,11 +39616,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -39843,26 +39676,6 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "mergeMathLists", "type": "boolean", @@ -39886,34 +39699,6 @@ } ] }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "latex", - "type": "latex", - "isArray": false - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -39976,9 +39761,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -40017,6 +39799,9 @@ { "name": "copySource" }, + { + "name": "assignNames" + }, { "name": "hide", "values": ["true", "false"] @@ -40026,8 +39811,7 @@ "values": ["true", "false"] }, { - "name": "fixed", - "values": ["true", "false"] + "name": "fixed" }, { "name": "fixLocation", @@ -40037,8 +39821,7 @@ "name": "styleNumber" }, { - "name": "isResponse", - "values": ["true", "false"] + "name": "isResponse" }, { "name": "newNamespace", @@ -40065,8 +39848,7 @@ "name": "displaySmallAsZero" }, { - "name": "padZeros", - "values": ["true", "false"] + "name": "padZeros" }, { "name": "functionSymbols" @@ -40099,11 +39881,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -40164,26 +39941,6 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "mergeMathLists", "type": "boolean", @@ -40207,34 +39964,6 @@ } ] }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "latex", - "type": "latex", - "isArray": false - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -40307,10 +40036,7 @@ "lorem", "subsetOfReals", "matrix", - "latex", - "mathList", - "tupleList", - "numberList" + "latex" ], "attributes": [ { @@ -40319,6 +40045,9 @@ { "name": "copySource" }, + { + "name": "assignNames" + }, { "name": "hide", "values": ["true", "false"] @@ -40328,8 +40057,7 @@ "values": ["true", "false"] }, { - "name": "fixed", - "values": ["true", "false"] + "name": "fixed" }, { "name": "fixLocation", @@ -40339,8 +40067,7 @@ "name": "styleNumber" }, { - "name": "isResponse", - "values": ["true", "false"] + "name": "isResponse" }, { "name": "newNamespace", @@ -40363,8 +40090,7 @@ "name": "displaySmallAsZero" }, { - "name": "padZeros", - "values": ["true", "false"] + "name": "padZeros" } ], "properties": [ @@ -40383,11 +40109,6 @@ "type": "integer", "isArray": false }, - { - "name": "isResponse", - "type": "boolean", - "isArray": false - }, { "name": "newNamespace", "type": "boolean", @@ -40433,67 +40154,11 @@ "type": "text", "isArray": false }, - { - "name": "displayDigits", - "type": "integer", - "isArray": false - }, - { - "name": "displayDecimals", - "type": "integer", - "isArray": false - }, - { - "name": "displaySmallAsZero", - "type": "number", - "isArray": false - }, - { - "name": "padZeros", - "type": "boolean", - "isArray": false - }, { "name": "numComponents", "type": "number", "isArray": false }, - { - "name": "numbers", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] - }, - { - "name": "math", - "type": "math", - "isArray": false - }, - { - "name": "maths", - "type": "math", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "math", - "numDimensions": 1 - } - ] - }, - { - "name": "text", - "type": "text", - "isArray": false - }, { "name": "numValues", "type": "number", @@ -40501,16 +40166,8 @@ }, { "name": "values", - "type": "number", - "isArray": true, - "numDimensions": 1, - "indexedArrayDescription": [ - { - "isArray": true, - "type": "number", - "numDimensions": 1 - } - ] + "type": "unknown", + "isArray": false } ], "top": true, @@ -41047,9 +40704,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "line", "angle", "mathInput", @@ -41400,9 +41054,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -41862,9 +41513,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -44403,9 +44051,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -44846,9 +44491,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -45266,9 +44908,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -45817,9 +45456,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -46005,9 +45641,6 @@ "h", "matrixInput", "text", - "mathList", - "tupleList", - "numberList", "point", "line", "angle", @@ -46207,9 +45840,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "line", "angle", "mathInput", @@ -46597,9 +46227,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "point", "line", "vector", @@ -46883,9 +46510,6 @@ "h", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -46915,6 +46539,9 @@ "bestFitLine", "matrix", "latex", + "mathList", + "tupleList", + "numberList", "xlabel", "ylabel", "not", @@ -46924,12 +46551,12 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", "label", "matchesPattern", + "textList", "booleanList" ], "attributes": [ @@ -47419,9 +47046,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -47461,13 +47085,11 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "hasSameFactoring", "label", "matchesPattern", "booleanInput", - "booleanList", "orbitalDiagram" ], "attributes": [ @@ -47817,9 +47439,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -47859,14 +47478,12 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", "label", "matchesPattern", "booleanInput", - "booleanList", "orbitalDiagram" ], "attributes": [ @@ -48146,9 +47763,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -48435,6 +48049,69 @@ "name": "rawRendererValue", "type": "latex", "isArray": false + }, + { + "name": "numDimensions", + "type": "integer", + "isArray": false + }, + { + "name": "vector", + "type": "math", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": false, + "type": "vector" + } + ] + }, + { + "name": "matrixSize", + "type": "numberList", + "isArray": false + }, + { + "name": "numRows", + "type": "integer", + "isArray": false + }, + { + "name": "numColumns", + "type": "integer", + "isArray": false + }, + { + "name": "matrix", + "type": "math", + "isArray": true, + "numDimensions": 2, + "indexedArrayDescription": [ + { + "isArray": false, + "type": "matrix" + }, + { + "isArray": false, + "type": "matrix" + } + ] + }, + { + "name": "x", + "type": "math", + "isArray": false + }, + { + "name": "y", + "type": "math", + "isArray": false + }, + { + "name": "z", + "type": "math", + "isArray": false } ], "top": true, @@ -48482,12 +48159,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -48719,6 +48392,60 @@ "name": "text", "type": "text", "isArray": false + }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] } ], "top": true, @@ -48778,9 +48505,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -49729,9 +49453,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "point", "line", "vector", @@ -49751,7 +49472,6 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", @@ -50042,9 +49762,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "point", "line", "vector", @@ -50064,7 +49781,6 @@ "isInteger", "isNumber", "isBetween", - "textList", "boolean", "when", "hasSameFactoring", @@ -50394,11 +50110,7 @@ "ion", "h", "text", - "textList", "boolean", - "mathList", - "tupleList", - "numberList", "when", "choice", "number", @@ -50825,9 +50537,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -51821,9 +51530,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -53121,11 +52827,7 @@ "isBetween", "atom", "ion", - "textList", "boolean", - "mathList", - "tupleList", - "numberList", "when", "choice", "hasSameFactoring", @@ -53645,9 +53347,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -53756,6 +53455,9 @@ "p", "boolean", "booleanList", + "mathList", + "tupleList", + "numberList", "collect", "ref", "lineSegment", @@ -54640,13 +54342,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -54951,13 +54648,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -55223,12 +54915,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -57811,13 +57499,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -59423,10 +59106,6 @@ { "name": "padZeros" }, - { - "name": "variantDeterminesSeed", - "values": ["true", "false"] - }, { "name": "asList", "values": ["true", "false"] @@ -59471,11 +59150,6 @@ "type": "text", "isArray": false }, - { - "name": "variantDeterminesSeed", - "type": "boolean", - "isArray": false - }, { "name": "numToSelect", "type": "integer", @@ -61682,12 +61356,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -61891,11 +61561,6 @@ "type": "point", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -61911,6 +61576,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "originalValue", "type": "intcomma", @@ -61966,12 +61685,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -62191,11 +61906,6 @@ "type": "point", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -62211,6 +61921,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "valuePrePluralize", "type": "pluralize", @@ -64925,13 +64689,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -65173,13 +64932,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -65454,9 +65208,6 @@ "ion", "ionicCompound", "matrixInput", - "mathList", - "tupleList", - "numberList", "line", "angle", "mathInput", @@ -66507,9 +66258,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -66960,9 +66708,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -69139,9 +68884,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -70002,13 +69744,8 @@ "displayDoenetML", "matrixInput", "text", - "textList", "boolean", - "booleanList", "math", - "mathList", - "tupleList", - "numberList", "collect", "ref", "point", @@ -70284,9 +70021,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -71044,9 +70778,6 @@ "matrixInput", "text", "math", - "mathList", - "tupleList", - "numberList", "point", "coords", "line", @@ -71277,12 +71008,8 @@ "electronConfiguration", "h", "text", - "textList", "boolean", "math", - "mathList", - "tupleList", - "numberList", "coords", "when", "choice", @@ -71513,11 +71240,6 @@ "type": "latex", "isArray": false }, - { - "name": "numCharacters", - "type": "integer", - "isArray": false - }, { "name": "text", "type": "text", @@ -71533,6 +71255,60 @@ "type": "number", "isArray": false }, + { + "name": "numWords", + "type": "integer", + "isArray": false + }, + { + "name": "words", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numCharacters", + "type": "integer", + "isArray": false + }, + { + "name": "characters", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, + { + "name": "numListItems", + "type": "integer", + "isArray": false + }, + { + "name": "listItems", + "type": "text", + "isArray": true, + "numDimensions": 1, + "indexedArrayDescription": [ + { + "isArray": true, + "type": "text", + "numDimensions": 1 + } + ] + }, { "name": "latex", "type": "latex", From 6622d009d6eb4f54045b5911cb6022883d44f26a Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 29 Oct 2024 20:11:48 -0500 Subject: [PATCH 10/13] numberLists merge math lists with single child --- .../src/components/MathList.js | 49 ++- .../src/components/NumberList.js | 371 +++++++++++++++--- .../src/components/RegionBetweenCurves.js | 4 +- packages/doenetml-worker/src/utils/label.js | 7 +- .../utils/mathVectorMatrixStateVariables.ts | 58 +++ packages/doenetml-worker/src/utils/text.ts | 4 +- 6 files changed, 418 insertions(+), 75 deletions(-) diff --git a/packages/doenetml-worker/src/components/MathList.js b/packages/doenetml-worker/src/components/MathList.js index 3f892359a..b17691e2e 100644 --- a/packages/doenetml-worker/src/components/MathList.js +++ b/packages/doenetml-worker/src/components/MathList.js @@ -227,7 +227,6 @@ export default class MathList extends CompositeComponent { let childValue = child.stateValues.value; if ( - childValue && Array.isArray(childValue.tree) && childValue.tree[0] === "list" ) { @@ -454,9 +453,26 @@ export default class MathList extends CompositeComponent { childIndex: 0, variableIndex: 0, }); + } else if ( + globalDependencyValues.mathsShadow !== null + ) { + if (!workspace.desiredMathShadow) { + workspace.desiredMathShadow = [ + ...globalDependencyValues.mathsShadow, + ]; + } + workspace.desiredMathShadow[arrayKey] = + desiredValue; } } + if (workspace.desiredMathShadow) { + instructions.push({ + setDependency: "mathsShadow", + desiredValue: workspace.desiredMathShadow, + }); + } + return { success: true, instructions, @@ -489,13 +505,16 @@ export default class MathList extends CompositeComponent { } workspace.desiredMathShadow[arrayKey] = desiredStateVariableValues.maths[arrayKey]; - instructions.push({ - setDependency: "mathsShadow", - desiredValue: workspace.desiredMathShadow, - }); } } + if (workspace.desiredMathShadow) { + instructions.push({ + setDependency: "mathsShadow", + desiredValue: workspace.desiredMathShadow, + }); + } + return { success: true, instructions, @@ -579,9 +598,9 @@ export default class MathList extends CompositeComponent { let numComponents = await component.stateValues.numComponents; for (let i = 0; i < numComponents; i++) { let childInfo = childInfoByComponent[i]; - let replacementSource = components[childInfo.childName]; + if (childInfo) { + let replacementSource = components[childInfo.childName]; - if (replacementSource) { if (childInfo.nComponents !== undefined) { componentsCopied.push( replacementSource.componentName + @@ -653,16 +672,12 @@ export default class MathList extends CompositeComponent { for (let childInfo of childInfoByComponent) { let replacementSource = components[childInfo.childName]; - if (replacementSource) { - if (childInfo.nComponents !== undefined) { - componentsToCopy.push( - replacementSource.componentName + - ":" + - childInfo.component, - ); - } else { - componentsToCopy.push(replacementSource.componentName); - } + if (childInfo.nComponents !== undefined) { + componentsToCopy.push( + replacementSource.componentName + ":" + childInfo.component, + ); + } else { + componentsToCopy.push(replacementSource.componentName); } } diff --git a/packages/doenetml-worker/src/components/NumberList.js b/packages/doenetml-worker/src/components/NumberList.js index b178d0b3e..8dd59c3ff 100644 --- a/packages/doenetml-worker/src/components/NumberList.js +++ b/packages/doenetml-worker/src/components/NumberList.js @@ -1,6 +1,8 @@ import CompositeComponent from "./abstract/CompositeComponent"; -import { returnRoundingAttributes } from "../utils/rounding"; +import me from "math-expressions"; import { returnGroupIntoComponentTypeSeparatedBySpacesOutsideParens } from "./commonsugar/lists"; +import { convertValueToMathExpression } from "@doenet/utils"; +import { returnRoundingAttributes } from "../utils/rounding"; import { convertAttributesForComponentType, postProcessCopy, @@ -89,6 +91,10 @@ export default class NumberList extends CompositeComponent { group: "numbers", componentTypes: ["number"], }, + { + group: "maths", + componentTypes: ["math"], + }, ]; } @@ -113,35 +119,119 @@ export default class NumberList extends CompositeComponent { }, }; - stateVariableDefinitions.numComponents = { + stateVariableDefinitions.mergeMathLists = { public: true, shadowingInstructions: { - createComponentOfType: "number", + createComponentOfType: "boolean", }, - additionalStateVariablesDefined: ["childNameByComponent"], returnDependencies: () => ({ - maxNumber: { - dependencyType: "stateVariable", - variableName: "maxNumber", + mergeMathListsAttr: { + dependencyType: "attributeComponent", + attributeName: "mergeMathLists", + variableNames: ["value"], + }, + mathChildren: { + dependencyType: "child", + childGroups: ["maths"], + skipComponentNames: true, }, numberChildren: { dependencyType: "child", childGroups: ["numbers"], - }, - numbersShadow: { - dependencyType: "stateVariable", - variableName: "numbersShadow", + skipComponentNames: true, }, }), + definition({ dependencyValues }) { + let mergeMathLists = + dependencyValues.mathChildren.length === 1 && + dependencyValues.numberChildren.length === 0; + return { setValue: { mergeMathLists } }; + }, + }; + + stateVariableDefinitions.numComponents = { + public: true, + shadowingInstructions: { + createComponentOfType: "number", + }, + stateVariablesDeterminingDependencies: ["mergeMathLists"], + additionalStateVariablesDefined: ["childInfoByComponent"], + returnDependencies({ stateValues }) { + let dependencies = { + maxNumber: { + dependencyType: "stateVariable", + variableName: "maxNumber", + }, + mergeMathLists: { + dependencyType: "stateVariable", + variableName: "mergeMathLists", + }, + numbersShadow: { + dependencyType: "stateVariable", + variableName: "numbersShadow", + }, + }; + + if (stateValues.mergeMathLists) { + dependencies.numberMathChildren = { + dependencyType: "child", + childGroups: ["numbers", "maths"], + variableNames: ["value"], + }; + } else { + dependencies.numberMathChildren = { + dependencyType: "child", + childGroups: ["numbers", "maths"], + }; + } + + return dependencies; + }, definition: function ({ dependencyValues }) { let numComponents = 0; - let childNameByComponent = []; - - if (dependencyValues.numberChildren.length > 0) { - childNameByComponent = dependencyValues.numberChildren.map( - (x) => x.componentName, - ); - numComponents = dependencyValues.numberChildren.length; + let childInfoByComponent = []; + + if (dependencyValues.numberMathChildren.length > 0) { + if (dependencyValues.mergeMathLists) { + for (let [ + childInd, + child, + ] of dependencyValues.numberMathChildren.entries()) { + let childValue = child.stateValues.value; + + if ( + Array.isArray(childValue.tree) && + childValue.tree[0] === "list" + ) { + let nComponents = childValue.tree.length - 1; + for (let i = 0; i < nComponents; i++) { + childInfoByComponent[i + numComponents] = { + childInd, + component: i, + nComponents, + childName: child.componentName, + }; + } + numComponents += nComponents; + } else { + childInfoByComponent[numComponents] = { + childInd, + childName: child.componentName, + }; + numComponents += 1; + } + } + } else { + numComponents = + dependencyValues.numberMathChildren.length; + childInfoByComponent = + dependencyValues.numberMathChildren.map( + (child, i) => ({ + childInd: i, + childName: child.componentName, + }), + ); + } } else if (dependencyValues.numbersShadow !== null) { numComponents = dependencyValues.numbersShadow.length; } @@ -149,14 +239,14 @@ export default class NumberList extends CompositeComponent { let maxNum = dependencyValues.maxNumber; if (numComponents > maxNum) { numComponents = maxNum; - childNameByComponent = childNameByComponent.slice( + childInfoByComponent = childInfoByComponent.slice( 0, maxNum, ); } return { - setValue: { numComponents, childNameByComponent }, + setValue: { numComponents, childInfoByComponent }, checkForActualChange: { numComponents: true }, }; }, @@ -168,7 +258,10 @@ export default class NumberList extends CompositeComponent { }, isArray: true, entryPrefixes: ["number"], - stateVariablesDeterminingDependencies: ["childNameByComponent"], + stateVariablesDeterminingDependencies: [ + "mergeMathLists", + "childInfoByComponent", + ], returnArraySizeDependencies: () => ({ numComponents: { dependencyType: "stateVariable", @@ -182,9 +275,13 @@ export default class NumberList extends CompositeComponent { returnArrayDependenciesByKey({ arrayKeys, stateValues }) { let dependenciesByKey = {}; let globalDependencies = { - childNameByComponent: { + mergeMathLists: { + dependencyType: "stateVariable", + variableName: "mergeMathLists", + }, + childInfoByComponent: { dependencyType: "stateVariable", - variableName: "childNameByComponent", + variableName: "childInfoByComponent", }, numbersShadow: { dependencyType: "stateVariable", @@ -194,13 +291,15 @@ export default class NumberList extends CompositeComponent { for (let arrayKey of arrayKeys) { let childIndices = []; - if (stateValues.childNameByComponent[arrayKey]) { - childIndices = [arrayKey]; + if (stateValues.childInfoByComponent[arrayKey]) { + childIndices = [ + stateValues.childInfoByComponent[arrayKey].childInd, + ]; } dependenciesByKey[arrayKey] = { - numberChildren: { + numberMathChildren: { dependencyType: "child", - childGroups: ["numbers"], + childGroups: ["numbers", "maths"], variableNames: ["value"], childIndices, }, @@ -217,10 +316,30 @@ export default class NumberList extends CompositeComponent { for (let arrayKey of arrayKeys) { let child = - dependencyValuesByKey[arrayKey].numberChildren[0]; + dependencyValuesByKey[arrayKey].numberMathChildren[0]; if (child) { - numbers[arrayKey] = child.stateValues.value; + let childValue = child.stateValues.value; + if ( + globalDependencyValues.mergeMathLists && + Array.isArray(childValue.tree) && + childValue.tree[0] === "list" + ) { + let ind2 = + globalDependencyValues.childInfoByComponent[ + arrayKey + ].component; + numbers[arrayKey] = childValue + .get_component(ind2) + .evaluate_to_constant(); + } else if ( + childValue.evaluate_to_constant !== undefined + ) { + numbers[arrayKey] = + childValue.evaluate_to_constant(); + } else { + numbers[arrayKey] = childValue; + } } else if (globalDependencyValues.numbersShadow !== null) { numbers[arrayKey] = globalDependencyValues.numbersShadow[arrayKey]; @@ -229,13 +348,135 @@ export default class NumberList extends CompositeComponent { return { setValue: { numbers } }; }, - inverseArrayDefinitionByKey({ + async inverseArrayDefinitionByKey({ desiredStateVariableValues, dependencyValuesByKey, globalDependencyValues, dependencyNamesByKey, + componentInfoObjects, + stateValues, workspace, }) { + if (globalDependencyValues.mergeMathLists) { + let instructions = []; + + let childInfoByComponent = + await stateValues.childInfoByComponent; + + let arrayKeysAddressed = []; + + for (let arrayKey in desiredStateVariableValues.numbers) { + if (!dependencyValuesByKey[arrayKey]) { + continue; + } + + if (arrayKeysAddressed.includes(arrayKey)) { + continue; + } + + let child = + dependencyValuesByKey[arrayKey] + .numberMathChildren[0]; + + let desiredValue; + if ( + childInfoByComponent[arrayKey].nComponents !== + undefined + ) { + // found a math that has been split due to merging + + // array keys that are associated with this math child + let firstInd = + Number(arrayKey) - + childInfoByComponent[arrayKey].component; + let lastInd = + firstInd + + childInfoByComponent[arrayKey].nComponents - + 1; + + // in case just one ind specified, merge with previous values + if (!workspace.desiredMaths) { + workspace.desiredMaths = []; + } + + let desiredTree = ["list"]; + + for (let i = firstInd; i <= lastInd; i++) { + if ( + desiredStateVariableValues.numbers[i] !== + undefined + ) { + workspace.desiredMaths[i] = + convertValueToMathExpression( + desiredStateVariableValues.numbers[ + i + ], + ); + } else if ( + workspace.desiredMaths[i] === undefined + ) { + workspace.desiredMaths[i] = me.fromAst( + (await stateValues.numbers)[i], + ); + } + + desiredTree.push( + workspace.desiredMaths[i].tree, + ); + arrayKeysAddressed.push(i.toString()); + } + + desiredValue = me.fromAst(desiredTree); + } else { + desiredValue = + desiredStateVariableValues.numbers[arrayKey]; + if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: + child?.componentType, + baseComponentType: "math", + }) + ) { + desiredValue = + convertValueToMathExpression(desiredValue); + } + } + + if (child) { + instructions.push({ + setDependency: + dependencyNamesByKey[arrayKey] + .numberMathChildren, + desiredValue, + childIndex: 0, + variableIndex: 0, + }); + } else if ( + globalDependencyValues.numbersShadow !== null + ) { + if (!workspace.desiredNumberShadow) { + workspace.desiredNumberShadow = [ + ...globalDependencyValues.numbersShadow, + ]; + } + workspace.desiredNumberShadow[arrayKey] = + desiredValue; + } + } + + if (workspace.desiredNumberShadow) { + instructions.push({ + setDependency: "numbersShadow", + desiredValue: workspace.desiredNumberShadow, + }); + } + + return { + success: true, + instructions, + }; + } + let instructions = []; for (let arrayKey in desiredStateVariableValues.numbers) { @@ -244,14 +485,25 @@ export default class NumberList extends CompositeComponent { } let child = - dependencyValuesByKey[arrayKey].numberChildren[0]; + dependencyValuesByKey[arrayKey].numberMathChildren[0]; if (child) { + let desiredValue = + desiredStateVariableValues.numbers[arrayKey]; + if ( + componentInfoObjects.isInheritedComponentType({ + inheritedComponentType: child.componentType, + baseComponentType: "math", + }) + ) { + desiredValue = + convertValueToMathExpression(desiredValue); + } instructions.push({ setDependency: - dependencyNamesByKey[arrayKey].numberChildren, - desiredValue: - desiredStateVariableValues.numbers[arrayKey], + dependencyNamesByKey[arrayKey] + .numberMathChildren, + desiredValue, childIndex: 0, variableIndex: 0, }); @@ -263,13 +515,16 @@ export default class NumberList extends CompositeComponent { } workspace.desiredNumberShadow[arrayKey] = desiredStateVariableValues.numbers[arrayKey]; - instructions.push({ - setDependency: "numbersShadow", - desiredValue: workspace.desiredNumberShadow, - }); } } + if (workspace.desiredNumberShadow) { + instructions.push({ + setDependency: "numbersShadow", + desiredValue: workspace.desiredNumberShadow, + }); + } + return { success: true, instructions, @@ -289,9 +544,9 @@ export default class NumberList extends CompositeComponent { stateVariableDefinitions.readyToExpandWhenResolved = { returnDependencies: () => ({ - childNameByComponent: { + childInfoByComponent: { dependencyType: "stateVariable", - variableName: "childNameByComponent", + variableName: "childInfoByComponent", }, }), // When this state variable is marked stale @@ -347,16 +602,24 @@ export default class NumberList extends CompositeComponent { }); } - let childNameByComponent = - await component.stateValues.childNameByComponent; + let childInfoByComponent = + await component.stateValues.childInfoByComponent; let numComponents = await component.stateValues.numComponents; for (let i = 0; i < numComponents; i++) { - let childName = childNameByComponent[i]; - let replacementSource = components[childName]; - - if (replacementSource) { - componentsCopied.push(replacementSource.componentName); + let childInfo = childInfoByComponent[i]; + if (childInfo) { + let replacementSource = components[childInfo.childName]; + + if (childInfo.nComponents !== undefined) { + componentsCopied.push( + replacementSource.componentName + + ":" + + childInfo.component, + ); + } else { + componentsCopied.push(replacementSource.componentName); + } } replacements.push({ componentType: "number", @@ -413,13 +676,17 @@ export default class NumberList extends CompositeComponent { let componentsToCopy = []; - let childNameByComponent = - await component.stateValues.childNameByComponent; + let childInfoByComponent = + await component.stateValues.childInfoByComponent; - for (let childName of childNameByComponent) { - let replacementSource = components[childName]; + for (let childInfo of childInfoByComponent) { + let replacementSource = components[childInfo.childName]; - if (replacementSource) { + if (childInfo.nComponents !== undefined) { + componentsToCopy.push( + replacementSource.componentName + ":" + childInfo.component, + ); + } else { componentsToCopy.push(replacementSource.componentName); } } diff --git a/packages/doenetml-worker/src/components/RegionBetweenCurves.js b/packages/doenetml-worker/src/components/RegionBetweenCurves.js index 7377e89d2..e9ad0f99b 100644 --- a/packages/doenetml-worker/src/components/RegionBetweenCurves.js +++ b/packages/doenetml-worker/src/components/RegionBetweenCurves.js @@ -120,8 +120,8 @@ export default class RegionBetweenCurves extends GraphicalComponent { dependencyValues.functions[1].stateValues.numOutputs !== 1 ) { return { - setValues: { - function: [() => NaN, () => NaN], + setValue: { + functions: [() => NaN, () => NaN], haveFunctions: false, fDefinitions: [{}, {}], }, diff --git a/packages/doenetml-worker/src/utils/label.js b/packages/doenetml-worker/src/utils/label.js index 860578af4..cb7782683 100644 --- a/packages/doenetml-worker/src/utils/label.js +++ b/packages/doenetml-worker/src/utils/label.js @@ -80,10 +80,13 @@ export function returnWrapNonLabelsSugarFunction({ ], }; } else { - // apply only if have a single string or multiple children to wrap + // apply only if have a single string/composite or multiple children to wrap if ( (childrenToWrap.length === 1 && - typeof childrenToWrap[0] !== "string") || + typeof childrenToWrap[0] !== "string" && + !componentInfoObjects.isCompositeComponent({ + componentType: childrenToWrap[0].componentType, + })) || childrenToWrap.length === 0 ) { return { success: false }; diff --git a/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts b/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts index 5abb425c7..74a07bdcf 100644 --- a/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts +++ b/packages/doenetml-worker/src/utils/mathVectorMatrixStateVariables.ts @@ -262,6 +262,64 @@ export function returnMathVectorMatrixStateVariableDefinitions() { targetVariableName: "x3", }; + stateVariableDefinitions.list = { + public: true, + shadowingInstructions: { + createComponentOfType: "math", + addAttributeComponentsShadowingStateVariables: + returnRoundingAttributeComponentShadowing(), + }, + isArray: true, + entryPrefixes: ["listItem"], + returnArraySizeDependencies: () => ({ + numDimensions: { + dependencyType: "stateVariable", + variableName: "numDimensions", + }, + }), + returnArraySize({ + dependencyValues, + }: { + dependencyValues: { numDimensions: number }; + }) { + return [dependencyValues.numDimensions]; + }, + returnArrayDependenciesByKey() { + let globalDependencies = { + vector: { + dependencyType: "stateVariable", + variableName: "vector", + }, + }; + return { globalDependencies }; + }, + arrayDefinitionByKey({ + globalDependencyValues, + }: { + globalDependencyValues: { vector: any }; + }) { + return { setValue: { list: globalDependencyValues.vector } }; + }, + + async inverseArrayDefinitionByKey({ + desiredStateVariableValues, + }: { + desiredStateVariableValues: { list: any[] }; + }) { + let instructions = [ + { + setDependency: "vector", + desiredValue: desiredStateVariableValues.list, + }, + ]; + + return { + success: true, + instructions, + }; + }, + }; + stateVariableDefinitions.matrixSize = { public: true, shadowingInstructions: { diff --git a/packages/doenetml-worker/src/utils/text.ts b/packages/doenetml-worker/src/utils/text.ts index 439ed8541..6f5a1f5e0 100644 --- a/packages/doenetml-worker/src/utils/text.ts +++ b/packages/doenetml-worker/src/utils/text.ts @@ -338,7 +338,7 @@ export function returnTextPieceStateVariableDefinitions() { }, }; - stateVariableDefinitions.listItems = { + stateVariableDefinitions.list = { public: true, shadowingInstructions: { createComponentOfType: "text", @@ -375,7 +375,7 @@ export function returnTextPieceStateVariableDefinitions() { }) { return { setValue: { - listItems: globalDependencyValues.value + list: globalDependencyValues.value .trim() .split(",") .map((s) => s.trim()), From 6f3df30b01ac566bd7169459997362e71cb9483f Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Wed, 30 Oct 2024 01:19:42 -0500 Subject: [PATCH 11/13] inverse definition of text will attempt to update a composite-generated list --- .../src/components/MathInput.js | 1 + .../src/components/MathList.js | 1 - .../doenetml-worker/src/components/Text.js | 39 +++++ .../src/components/TextInput.js | 1 + .../src/test/tagSpecific/boolean.test.ts | 2 +- .../src/test/tagSpecific/callAction.test.ts | 99 +++++++------ .../src/test/tagSpecific/mathlist.test.ts | 41 ++++++ .../src/test/tagSpecific/numberlist.test.ts | 135 +++++++++++++++++- .../src/test/tagSpecific/text.test.ts | 6 +- .../src/test/tagSpecific/textinput.test.ts | 10 +- .../src/test/tagSpecific/textlist.test.ts | 40 ++++++ .../src/test/tagSpecific/triggerset.test.ts | 14 +- packages/doenetml-worker/src/utils/label.js | 9 +- 13 files changed, 326 insertions(+), 72 deletions(-) diff --git a/packages/doenetml-worker/src/components/MathInput.js b/packages/doenetml-worker/src/components/MathInput.js index a7bec2a08..77e592ed0 100644 --- a/packages/doenetml-worker/src/components/MathInput.js +++ b/packages/doenetml-worker/src/components/MathInput.js @@ -147,6 +147,7 @@ export default class MathInput extends Input { sugarInstructions.push({ replacementFunction: returnWrapNonLabelsSugarFunction({ wrappingComponentType: "math", + wrapSingleIfNotWrappingComponentType: true, }), }); diff --git a/packages/doenetml-worker/src/components/MathList.js b/packages/doenetml-worker/src/components/MathList.js index b17691e2e..65ce2e8f0 100644 --- a/packages/doenetml-worker/src/components/MathList.js +++ b/packages/doenetml-worker/src/components/MathList.js @@ -277,7 +277,6 @@ export default class MathList extends CompositeComponent { }; stateVariableDefinitions.maths = { - public: true, shadowingInstructions: { createComponentOfType: "math", }, diff --git a/packages/doenetml-worker/src/components/Text.js b/packages/doenetml-worker/src/components/Text.js index 8ec308677..bc1e9908d 100644 --- a/packages/doenetml-worker/src/components/Text.js +++ b/packages/doenetml-worker/src/components/Text.js @@ -169,9 +169,48 @@ export default class Text extends InlineComponent { dependencyValues, }) { let numChildren = dependencyValues.textLikeChildren.length; + if (numChildren > 1) { + // if have multiple children, then we could still update them if + // 1. all children come from a single composite with asList set to true, and + // 2. the desired value is a comma-separated list with the number of entries + // matching the number of children. + // In that case, we will attempt to update each child to the corresponding entry + // from the desired value. + + // Check if all text children are from a composite with asList set to true + let foundAllFromListComposite = false; + for (let range of dependencyValues.textLikeChildren + .compositeReplacementRange) { + if ( + range.asList && + range.firstInd === 0 && + range.lastInd === numChildren - 1 + ) { + foundAllFromListComposite = true; + } + } + + if (foundAllFromListComposite) { + // Check if desired value is a comma-separated list with the same number of entries as children + let splitValues = desiredStateVariableValues.value + .split(",") + .map((v) => v.trim()); + + if (splitValues.length === numChildren) { + // All conditions are met, so we attempt to update the children + let instructions = splitValues.map((v, i) => ({ + setDependency: "textLikeChildren", + desiredValue: v, + childIndex: i, + variableIndex: 0, + })); + return { success: true, instructions }; + } + } return { success: false }; } + if (numChildren === 1) { return { success: true, diff --git a/packages/doenetml-worker/src/components/TextInput.js b/packages/doenetml-worker/src/components/TextInput.js index bd2c4b624..8f801059e 100644 --- a/packages/doenetml-worker/src/components/TextInput.js +++ b/packages/doenetml-worker/src/components/TextInput.js @@ -101,6 +101,7 @@ export default class Textinput extends Input { sugarInstructions.push({ replacementFunction: returnWrapNonLabelsSugarFunction({ wrappingComponentType: "text", + wrapSingleIfNotWrappingComponentType: true, }), }); diff --git a/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts b/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts index 549a14acd..26188e1e2 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/boolean.test.ts @@ -1009,7 +1009,7 @@ describe("Boolean tag tests", async () => { isSupersetCaseInsensitive: false, }, { - set1: "a, c", + set1: "a, c", set2: "", isSubset: true, isSubsetCaseInsensitive: true, diff --git a/packages/doenetml-worker/src/test/tagSpecific/callAction.test.ts b/packages/doenetml-worker/src/test/tagSpecific/callAction.test.ts index 924bec46a..4d6910e7d 100644 --- a/packages/doenetml-worker/src/test/tagSpecific/callAction.test.ts +++ b/packages/doenetml-worker/src/test/tagSpecific/callAction.test.ts @@ -5,7 +5,6 @@ import { updateBooleanInputValue, updateMathInputValue, } from "../utils/actions"; -import me from "math-expressions"; const Mock = vi.fn(); vi.stubGlobal("postMessage", Mock); @@ -18,7 +17,7 @@ describe("callAction tag tests", async () => { let numbers = stateVariables["/nums"].stateValues.text .split(",") .map(Number); - expect(numbers.length).eq(5); + expect(numbers.length).eq(7); for (let [ind, num] of numbers.entries()) { expect(Number.isInteger(num)).be.true; expect(num).gte(1); @@ -42,7 +41,7 @@ describe("callAction tag tests", async () => { .split(",") .map(Number); - expect(numbers2.length).eq(5); + expect(numbers2.length).eq(7); for (let num of numbers2) { expect(Number.isInteger(num)).be.true; expect(num).gte(1); @@ -54,7 +53,7 @@ describe("callAction tag tests", async () => { it("resample random numbers", async () => { let core = await createTestCore({ doenetML: ` -

+

@@ -373,7 +372,7 @@ describe("callAction tag tests", async () => { let numbers = stateVariables["/nums"].stateValues.text .split(",") .map(Number); - expect(numbers.length).eq(5); + expect(numbers.length).eq(7); for (let num of numbers) { expect(Number.isInteger(num)).be.true; expect(num).gte(1); @@ -424,7 +423,7 @@ describe("callAction tag tests", async () => { .split(",") .map(Number); - expect(numbers2.length).eq(5); + expect(numbers2.length).eq(7); for (let num of numbers2) { expect(Number.isInteger(num)).be.true; expect(num).gte(1); @@ -436,7 +435,7 @@ describe("callAction tag tests", async () => { it("chained actions", async () => { let core = await createTestCore({ doenetML: ` -

+

@@ -461,7 +460,7 @@ describe("callAction tag tests", async () => { it("chained actions, unnecessary $", async () => { let core = await createTestCore({ doenetML: ` -

+

@@ -488,7 +487,7 @@ describe("callAction tag tests", async () => { doenetML: `