Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handGraded attribute for answer #1977

Merged
merged 1 commit into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -22009,4 +22009,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 @@ -259,6 +265,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 @@ -294,6 +301,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 @@ -400,6 +408,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 @@ -581,14 +593,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 @@ -638,6 +654,10 @@ export default class Answer extends InlineComponent {
haveAwardThatRequiresInput: {
dependencyType: "stateVariable",
variableName: "haveAwardThatRequiresInput"
},
handGraded: {
dependencyType: "stateVariable",
variableName: "handGraded"
}
};

Expand Down Expand Up @@ -667,7 +687,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 @@ -1245,7 +1265,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 @@ -1740,6 +1760,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