Skip to content

Commit

Permalink
handGraded attribute for answer (#1977)
Browse files Browse the repository at this point in the history
  • Loading branch information
dqnykamp authored Mar 7, 2023
1 parent c4955ac commit b7c8f3a
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 3 deletions.
278 changes: 278 additions & 0 deletions cypress/e2e/DoenetML/tagSpecific/answer.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -22177,4 +22177,282 @@ describe('Answer Tag Tests', function () {

});

it('hand-graded answers', () => {
cy.window().then(async (win) => {
win.postMessage({
doenetML: `
<text>a</text>
<p>Maths:
<answer handGraded name="m1" />
<answer handGraded type="math" name="m2" />
<answer handGraded name="m3" ><mathinput /></answer>
<answer handGraded name="m4">x</answer>
<answer handGraded name="m5"><math>x</math></answer>
<answer handGraded name="m6"><award><math>x</math></award></answer>
<answer handGraded name="m7"><mathinput /><award><math>x</math></award></answer>
</p>
<p>Submitted responses:
<math name="m1sr" copySource="m1" />
<math name="m2sr" copySource="m2" />
<math name="m3sr" copySource="m3" />
<math name="m4sr" copySource="m4" />
<math name="m5sr" copySource="m5" />
<math name="m6sr" copySource="m6" />
<math name="m7sr" copySource="m7" />
</p>
<p>Credit achieved:
<number name="m1ca" copySource="m1.creditAchieved" />
<number name="m2ca" copySource="m2.creditAchieved" />
<number name="m3ca" copySource="m3.creditAchieved" />
<number name="m4ca" copySource="m4.creditAchieved" />
<number name="m5ca" copySource="m5.creditAchieved" />
<number name="m6ca" copySource="m6.creditAchieved" />
<number name="m7ca" copySource="m7.creditAchieved" />
</p>
<p>Texts:
<answer handGraded type="text" name="t1" />
<answer handGraded name="t2"><textinput /></answer>
<answer handGraded name="t3"><text>hello</text></answer>
<answer handGraded name="t4"><award><text>hello</text></award></answer>
</p>
<p>Submitted responses:
<text name="t1sr" copySource="t1" />
<text name="t2sr" copySource="t2" />
<text name="t3sr" copySource="t3" />
<text name="t4sr" copySource="t4" />
</p>
<p>Credit achieved:
<number name="t1ca" copySource="t1.creditAchieved" />
<number name="t2ca" copySource="t2.creditAchieved" />
<number name="t3ca" copySource="t3.creditAchieved" />
<number name="t4ca" copySource="t4.creditAchieved" />
</p>
<p>Multiple inputs
<answer handGraded name="mi1"><mathinput /><mathinput /></answer>
<answer handGraded name="mi2"><textinput /><textinput /></answer>
<answer handGraded name="mi3"><mathinput /><textinput /></answer>
</p>
<p>Submitted responses:
<math name="mi1sr1" copySource="mi1.submittedResponse1" />
<math name="mi1sr2" copySource="mi1.submittedResponse2" />
<text name="mi2sr1" copySource="mi2.submittedResponse1" />
<text name="mi2sr2" copySource="mi2.submittedResponse2" />
<math name="mi3sr1" copySource="mi3.submittedResponse1" />
<text name="mi3sr2" copySource="mi3.submittedResponse2" />
</p>
<p>Credit achieved:
<number name="mi1ca" copySource="mi1.creditAchieved" />
<number name="mi2ca" copySource="mi2.creditAchieved" />
<number name="mi3ca" copySource="mi3.creditAchieved" />
</p>
<p>Inputs outside answer
<mathinput name="oi1i1" /> <mathinput name="oi1i2" /> <answer handGraded name="oi1"><award><when>$oi1i1$oi1i2</when></award></answer>
<textinput name="oi2i1" /> <textinput name="oi2i2" /> <answer handGraded name="oi2"><award><when>$oi2i1$oi2i2</when></award></answer>
<mathinput name="oi3i1" /> <textinput name="oi3i2" /> <answer handGraded name="oi3"><award><when>$oi3i1$oi3i2</when></award></answer>
</p>
<p>Submitted responses:
<math name="oi1sr1" copySource="oi1.submittedResponse1" />
<math name="oi1sr2" copySource="oi1.submittedResponse2" />
<text name="oi2sr1" copySource="oi2.submittedResponse1" />
<text name="oi2sr2" copySource="oi2.submittedResponse2" />
<math name="oi3sr1" copySource="oi3.submittedResponse1" />
<text name="oi3sr2" copySource="oi3.submittedResponse2" />
</p>
<p>Credit achieved:
<number name="oi1ca" copySource="oi1.creditAchieved" />
<number name="oi2ca" copySource="oi2.creditAchieved" />
<number name="oi3ca" copySource="oi3.creditAchieved" />
</p>


`}, "*");
});


cy.get("#\\/_text1").should("have.text", "a");

cy.get('#\\/m1 textarea').type("x{enter}", { force: true })
cy.get('#\\/m2 textarea').type("x{enter}", { force: true })
cy.get('#\\/m3 textarea').type("x{enter}", { force: true })
cy.get('#\\/m4 textarea').type("x{enter}", { force: true })
cy.get('#\\/m5 textarea').type("x{enter}", { force: true })
cy.get('#\\/m6 textarea').type("x{enter}", { force: true })
cy.get('#\\/m7 textarea').type("x{enter}", { force: true })

cy.get('#\\/t1 input').type("hello{enter}")
cy.get('#\\/t2 input').type("hello{enter}")
cy.get('#\\/t3 input').type("hello{enter}")
cy.get('#\\/t4 input').type("hello{enter}")

cy.get('#\\/mi1 textarea').eq(0).type("x{enter}", { force: true })
cy.get('#\\/mi1 textarea').eq(1).type("y{enter}", { force: true })
cy.get('#\\/mi1_submit').click();
cy.get('#\\/mi2 input').eq(0).type("hello{enter}")
cy.get('#\\/mi2 input').eq(1).type("bye{enter}")
cy.get('#\\/mi2_submit').click();
cy.get('#\\/mi3 textarea').type("x{enter}", { force: true })
cy.get('#\\/mi3 input').type("bye{enter}")
cy.get('#\\/mi3_submit').click();


cy.get('#\\/oi1i1 textarea').type("x{enter}", { force: true })
cy.get('#\\/oi1i2 textarea').type("y{enter}", { force: true })
cy.get('#\\/oi1_submit').click();
cy.get('#\\/oi2i1 input').type("hello{enter}")
cy.get('#\\/oi2i2 input').type("bye{enter}")
cy.get('#\\/oi2_submit').click();
cy.get('#\\/oi3i1 textarea').type("x{enter}", { force: true })
cy.get('#\\/oi3i2 input').type("bye{enter}")
cy.get('#\\/oi3_submit').click();


cy.get('#\\/oi3sr2').should('have.text', 'bye')

cy.get('#\\/m1sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m2sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m3sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m4sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m5sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m6sr .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/m7sr .mjx-mrow').eq(0).should('have.text', "x")

cy.get('#\\/t1sr').should('have.text', "hello")
cy.get('#\\/t2sr').should('have.text', "hello")
cy.get('#\\/t3sr').should('have.text', "hello")
cy.get('#\\/t4sr').should('have.text', "hello")

cy.get('#\\/mi1sr1 .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/mi1sr2 .mjx-mrow').eq(0).should('have.text', "y")
cy.get('#\\/mi2sr1').should('have.text', "hello")
cy.get('#\\/mi2sr2').should('have.text', "bye")
cy.get('#\\/mi3sr1 .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/mi3sr2').should('have.text', "bye")

cy.get('#\\/oi1sr1 .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/oi1sr2 .mjx-mrow').eq(0).should('have.text', "y")
cy.get('#\\/oi2sr1').should('have.text', "hello")
cy.get('#\\/oi2sr2').should('have.text', "bye")
cy.get('#\\/oi3sr1 .mjx-mrow').eq(0).should('have.text', "x")
cy.get('#\\/oi3sr2').should('have.text', "bye")


cy.get('#\\/m1ca').should('have.text', '0')
cy.get('#\\/m2ca').should('have.text', '0')
cy.get('#\\/m3ca').should('have.text', '0')
cy.get('#\\/m4ca').should('have.text', '0')
cy.get('#\\/m5ca').should('have.text', '0')
cy.get('#\\/m6ca').should('have.text', '0')
cy.get('#\\/m7ca').should('have.text', '0')

cy.get('#\\/t1ca').should('have.text', '0')
cy.get('#\\/t2ca').should('have.text', '0')
cy.get('#\\/t3ca').should('have.text', '0')
cy.get('#\\/t4ca').should('have.text', '0')

cy.get('#\\/mi1ca').should('have.text', '0')
cy.get('#\\/mi2ca').should('have.text', '0')
cy.get('#\\/mi3ca').should('have.text', '0')

cy.get('#\\/oi1ca').should('have.text', '0')
cy.get('#\\/oi2ca').should('have.text', '0')
cy.get('#\\/oi3ca').should('have.text', '0')


cy.log("revise answers and submit")

cy.get('#\\/m1 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m2 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m3 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m4 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m5 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m6 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/m7 textarea').type("{end}y{enter}", { force: true })

cy.get('#\\/t1 input').type(" there{enter}")
cy.get('#\\/t2 input').type(" there{enter}")
cy.get('#\\/t3 input').type(" there{enter}")
cy.get('#\\/t4 input').type(" there{enter}")


cy.get('#\\/mi1 textarea').eq(0).type("{end}z{enter}", { force: true })
cy.get('#\\/mi1_submit').click();
cy.get('#\\/mi1 textarea').eq(1).type("{end}z{enter}", { force: true })
cy.get('#\\/mi1_submit').click();
cy.get('#\\/mi2 input').eq(0).type(" there{enter}")
cy.get('#\\/mi2_submit').click();
cy.get('#\\/mi2 input').eq(1).type(" now{enter}")
cy.get('#\\/mi2_submit').click();
cy.get('#\\/mi3 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/mi3_submit').click();
cy.get('#\\/mi3 input').type(" now{enter}")
cy.get('#\\/mi3_submit').click();



cy.get('#\\/oi1i1 textarea').type("{end}z{enter}", { force: true })
cy.get('#\\/oi1_submit').click();
cy.get('#\\/oi1i2 textarea').type("{end}z{enter}", { force: true })
cy.get('#\\/oi1_submit').click();
cy.get('#\\/oi2i1 input').type(" there{enter}")
cy.get('#\\/oi2_submit').click();
cy.get('#\\/oi2i2 input').type(" now{enter}")
cy.get('#\\/oi2_submit').click();
cy.get('#\\/oi3i1 textarea').type("{end}y{enter}", { force: true })
cy.get('#\\/oi3_submit').click();
cy.get('#\\/oi3i2 input').type(" now{enter}")
cy.get('#\\/oi3_submit').click();


cy.get('#\\/oi3sr2').should('have.text', 'bye now')

cy.get('#\\/m1sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m2sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m3sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m4sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m5sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m6sr .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/m7sr .mjx-mrow').eq(0).should('have.text', "xy")

cy.get('#\\/t1sr').should('have.text', "hello there")
cy.get('#\\/t2sr').should('have.text', "hello there")
cy.get('#\\/t3sr').should('have.text', "hello there")
cy.get('#\\/t4sr').should('have.text', "hello there")

cy.get('#\\/mi1sr1 .mjx-mrow').eq(0).should('have.text', "xz")
cy.get('#\\/mi1sr2 .mjx-mrow').eq(0).should('have.text', "yz")
cy.get('#\\/mi2sr1').should('have.text', "hello there")
cy.get('#\\/mi2sr2').should('have.text', "bye now")
cy.get('#\\/mi3sr1 .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/mi3sr2').should('have.text', "bye now")

cy.get('#\\/oi1sr1 .mjx-mrow').eq(0).should('have.text', "xz")
cy.get('#\\/oi1sr2 .mjx-mrow').eq(0).should('have.text', "yz")
cy.get('#\\/oi2sr1').should('have.text', "hello there")
cy.get('#\\/oi2sr2').should('have.text', "bye now")
cy.get('#\\/oi3sr1 .mjx-mrow').eq(0).should('have.text', "xy")
cy.get('#\\/oi3sr2').should('have.text', "bye now")


cy.get('#\\/m1ca').should('have.text', '0')
cy.get('#\\/m2ca').should('have.text', '0')
cy.get('#\\/m3ca').should('have.text', '0')
cy.get('#\\/m4ca').should('have.text', '0')
cy.get('#\\/m5ca').should('have.text', '0')
cy.get('#\\/m6ca').should('have.text', '0')
cy.get('#\\/m7ca').should('have.text', '0')

cy.get('#\\/t1ca').should('have.text', '0')
cy.get('#\\/t2ca').should('have.text', '0')
cy.get('#\\/t3ca').should('have.text', '0')
cy.get('#\\/t4ca').should('have.text', '0')

cy.get('#\\/mi1ca').should('have.text', '0')
cy.get('#\\/mi2ca').should('have.text', '0')
cy.get('#\\/mi3ca').should('have.text', '0')

cy.get('#\\/oi1ca').should('have.text', '0')
cy.get('#\\/oi2ca').should('have.text', '0')
cy.get('#\\/oi3ca').should('have.text', '0')
});

})
29 changes: 26 additions & 3 deletions src/Core/components/Answer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export default class Answer extends InlineComponent {
defaultValue: 1,
public: true,
};
attributes.handGraded = {
createPrimitiveOfType: "boolean",
createStateVariable: "handGraded",
defaultValue: false,
public: true,
};
attributes.inline = {
createComponentOfType: "boolean",
createStateVariable: "inline",
Expand Down Expand Up @@ -266,6 +272,7 @@ export default class Answer extends InlineComponent {
let nChoicesFound = 0;
let definitelyDoNotAddInput = false, mayNeedInput = false;
let foundResponse = false;
let foundAward = false;

let childIsWrappable = [];
for (let child of matchedChildren) {
Expand Down Expand Up @@ -301,6 +308,7 @@ export default class Answer extends InlineComponent {
childIsWrappable.push(true);
nChoicesFound++;
} else if (componentIsSpecifiedType(child, "award")) {
foundAward = true;
childIsWrappable.push(false);
if (child.attributes?.sourcesAreResponses) {
foundResponse = true;
Expand Down Expand Up @@ -407,6 +415,10 @@ export default class Answer extends InlineComponent {
}


if (componentAttributes.handGraded && !foundAward) {
mayNeedInput = true;
}

if (!mayNeedInput && !foundResponse) {
// recurse to all descendants of awards to see if have a response
for (let child of matchedChildren) {
Expand Down Expand Up @@ -588,14 +600,18 @@ export default class Answer extends InlineComponent {
showCorrectnessFlag: {
dependencyType: "flag",
flagName: "showCorrectness"
},
handGraded: {
dependencyType: "stateVariable",
variableName: "handGraded"
}
}),
definition({ dependencyValues, usedDefault }) {
let showCorrectness;
if (!usedDefault.showCorrectnessPreliminary) {
showCorrectness = dependencyValues.showCorrectnessPreliminary
} else {
showCorrectness = dependencyValues.showCorrectnessFlag !== false;
showCorrectness = dependencyValues.showCorrectnessFlag !== false && !dependencyValues.handGraded;
}
return { setValue: { showCorrectness } }
}
Expand Down Expand Up @@ -645,6 +661,10 @@ export default class Answer extends InlineComponent {
haveAwardThatRequiresInput: {
dependencyType: "stateVariable",
variableName: "haveAwardThatRequiresInput"
},
handGraded: {
dependencyType: "stateVariable",
variableName: "handGraded"
}
};

Expand Down Expand Up @@ -674,7 +694,7 @@ export default class Answer extends InlineComponent {
let skipFirstSugaredInput =
inputChildren[0]?.componentType !== "choiceInput"
&& (
!dependencyValues.haveAwardThatRequiresInput
!(dependencyValues.haveAwardThatRequiresInput || dependencyValues.handGraded)
|| dependencyValues.allInputChildrenIncludingSugared.length > 1
);

Expand Down Expand Up @@ -1252,7 +1272,7 @@ export default class Answer extends InlineComponent {
inputChildren: {
dependencyType: "child",
childGroups: ["inputs"],
variableNames: ["creditAchievedIfSubmit"],
variableNames: ["creditAchievedIfSubmit", "value"], // include value so inputs always make dependency values change
childIndices: stateValues.inputChildIndices,
variablesOptional: true,
},
Expand Down Expand Up @@ -1747,6 +1767,9 @@ export default class Answer extends InlineComponent {
}

let creditAchieved = await this.stateValues.creditAchievedIfSubmit;
if (await this.stateValues.handGraded) {
creditAchieved = 0;
}
let awardsUsed = await this.stateValues.awardsUsedIfSubmit;
let inputUsed = await this.stateValues.inputUsedIfSubmit;

Expand Down

0 comments on commit b7c8f3a

Please sign in to comment.