diff --git a/package-lock.json b/package-lock.json
index 6d72297c9..10176fe6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22819,7 +22819,7 @@
},
"packages/doenetml": {
"name": "@doenet/doenetml",
- "version": "0.7.0-alpha19",
+ "version": "0.7.0-alpha20",
"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-alpha19",
+ "version": "0.7.0-alpha20",
"license": "AGPL-3.0-or-later",
"devDependencies": {},
"peerDependencies": {
@@ -22939,7 +22939,7 @@
},
"packages/standalone": {
"name": "@doenet/standalone",
- "version": "0.7.0-alpha19",
+ "version": "0.7.0-alpha20",
"license": "AGPL-3.0-or-later",
"devDependencies": {}
},
diff --git a/packages/doenetml-iframe/package.json b/packages/doenetml-iframe/package.json
index ba01a4216..789d40702 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-alpha19",
+ "version": "0.7.0-alpha20",
"license": "AGPL-3.0-or-later",
"homepage": "https://github.com/Doenet/DoenetML#readme",
"private": true,
diff --git a/packages/doenetml-worker/src/Core.js b/packages/doenetml-worker/src/Core.js
index f0b44c84a..e452b1d11 100644
--- a/packages/doenetml-worker/src/Core.js
+++ b/packages/doenetml-worker/src/Core.js
@@ -196,33 +196,38 @@ export default class Core {
this.parameterStack.parameters.prerender = prerender;
this.initialized = false;
- this.initializedPromiseResolves = [];
+ this.initializedPromiseResolveRejects = [];
this.postInitializedMessages = [];
this.resolveInitialized = () => {
- this.initializedPromiseResolves.forEach((resolve) => resolve(true));
+ this.initializedPromiseResolveRejects.forEach(({ resolve }) =>
+ resolve(true),
+ );
this.initialized = true;
for (let message of this.postInitializedMessages) {
postMessage(message);
}
this.postInitializedMessages = [];
};
+ this.rejectInitialized = (e) => {
+ this.initializedPromiseResolveRejects.forEach(({ reject }) =>
+ reject(e),
+ );
+ };
this.getInitializedPromise = () => {
if (this.initialized) {
return Promise.resolve(true);
} else {
return new Promise((resolve, reject) => {
- this.initializedPromiseResolves.push(resolve);
+ this.initializedPromiseResolveRejects.push({
+ resolve,
+ reject,
+ });
});
}
};
this.finishCoreConstruction().catch((e) => {
- // throw e;
- postMessage({
- messageType: "inErrorState",
- coreId: this.coreId,
- args: { errMsg: e.message },
- });
+ this.rejectInitialized(e);
});
}
diff --git a/packages/doenetml-worker/src/CoreWorker.js b/packages/doenetml-worker/src/CoreWorker.js
index 51002fcce..1b02bbfc7 100644
--- a/packages/doenetml-worker/src/CoreWorker.js
+++ b/packages/doenetml-worker/src/CoreWorker.js
@@ -174,13 +174,21 @@ async function createCore(args) {
Object.assign(coreArgs, args);
core = new Core(coreArgs);
- core.getInitializedPromise().then(() => {
+
+ try {
+ await core.getInitializedPromise();
// console.log('actions to process', queuedRequestActions)
for (let action of queuedRequestActions) {
core.requestAction(action);
}
queuedRequestActions = [];
- });
+ } catch (e) {
+ postMessage({
+ messageType: "inErrorState",
+ coreId: coreArgs.coreId,
+ args: { errMsg: e.message },
+ });
+ }
} else {
let errMsg =
initializeResult.success === false
diff --git a/packages/doenetml-worker/src/components/Answer.js b/packages/doenetml-worker/src/components/Answer.js
index e6a458301..91b82a4d2 100644
--- a/packages/doenetml-worker/src/components/Answer.js
+++ b/packages/doenetml-worker/src/components/Answer.js
@@ -1710,22 +1710,46 @@ export default class Answer extends InlineComponent {
includeOnlyEssentialValues: true,
},
}),
- definition({ dependencyValues }) {
+ definition({ dependencyValues, componentName }) {
// Use stringify from json-stringify-deterministic
// so that the string will be the same
// even if the object was built in a different order
// (as can happen when reloading from a database)
+ let warnings = [];
+
+ let selfDependencies =
+ dependencyValues.currentCreditAchievedDependencies.find(
+ (x) => x.componentName === componentName,
+ );
+
+ if (selfDependencies) {
+ // look for a dependency on a submitted response
+ if (
+ Object.keys(selfDependencies.stateValues).find(
+ (x) => x.substring(0, 17) === "submittedResponse",
+ )
+ ) {
+ warnings.push({
+ message:
+ "An award for this answer is based on the answer's own submitted response, which will lead to unexpected behavior.",
+ level: 1,
+ });
+ }
+ }
+
let stringified = stringify(
dependencyValues.currentCreditAchievedDependencies,
{ replacer: serializedComponentsReplacer },
);
+
return {
setValue: {
creditAchievedDependencies: Base64.stringify(
sha1(stringified),
),
},
+ sendWarnings: warnings,
};
},
markStale: () => ({ answerCreditPotentiallyChanged: true }),
diff --git a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts
index 96fd72580..9f83627ea 100644
--- a/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts
+++ b/packages/doenetml-worker/src/test/tagSpecific/answer.test.ts
@@ -1428,6 +1428,124 @@ describe("Answer tag tests", async () => {
expect(stateVariables["/answer1"].stateValues.creditAchieved).eq(1);
});
+ it("warning for award depending on submitted response", async () => {
+ const doenetMLs = [
+ `
+
+
+ $ans
+ `,
+ `
+
+
+ $ans.submittedResponse=5
+ `,
+ `
+
+
+ $ans.submittedResponse1=5
+ `,
+ `
+
+
+ $ans.submittedResponses=5
+ `,
+ ];
+
+ async function check_award_based_on_submitted_response(
+ core: any,
+ eventually_correct = true,
+ ) {
+ let errorWarnings = core.errorWarnings;
+
+ expect(errorWarnings.errors.length).eq(0);
+ expect(errorWarnings.warnings.length).eq(1);
+
+ expect(errorWarnings.warnings[0].message).contain(
+ "An award for this answer is based on the answer's own submitted response",
+ );
+ expect(errorWarnings.warnings[0].level).eq(1);
+ expect(errorWarnings.warnings[0].doenetMLrange.lineBegin).eq(2);
+ expect(errorWarnings.warnings[0].doenetMLrange.charBegin).eq(5);
+ expect(errorWarnings.warnings[0].doenetMLrange.lineEnd).eq(5);
+ expect(errorWarnings.warnings[0].doenetMLrange.charEnd).eq(13);
+
+ let stateVariables = await returnAllStateVariables(core);
+ let mathInputName =
+ stateVariables["/ans"].stateValues.inputChildren[0]
+ .componentName;
+
+ // have to submit the correct answer twice before it is marked correct
+ await updateMathInputValue({
+ latex: "5",
+ componentName: mathInputName,
+ core,
+ });
+ await core.requestAction({
+ componentName: "/ans",
+ actionName: "submitAnswer",
+ args: {},
+ event: null,
+ });
+ stateVariables = await returnAllStateVariables(core);
+
+ // answer is not correct because the submitted response was initially blank
+ expect(stateVariables["/ans"].stateValues.creditAchieved).eq(0);
+ // justSubmitted becomes false at the criteria (based on submitted response)
+ // change when the answer is submitted
+ expect(stateVariables["/ans"].stateValues.justSubmitted).eq(false);
+
+ await core.requestAction({
+ componentName: "/ans",
+ actionName: "submitAnswer",
+ args: {},
+ event: null,
+ });
+ stateVariables = await returnAllStateVariables(core);
+
+ // if `eventually correct` is set to `true`, then
+ // the second time, the answer is marked correct and justSubmitted stays true
+ // because the submitted response starts off correct and doesn't change
+ expect(stateVariables["/ans"].stateValues.creditAchieved).eq(
+ eventually_correct ? 1 : 0,
+ );
+ expect(stateVariables["/ans"].stateValues.justSubmitted).eq(true);
+ }
+
+ for (let doenetML of doenetMLs) {
+ let core = await createTestCore({
+ doenetML,
+ });
+ await check_award_based_on_submitted_response(core);
+ }
+
+ let doenetML = `
+
+
+ $ans.submittedResponse2=5
+ `;
+
+ let core = await createTestCore({
+ doenetML,
+ });
+ await check_award_based_on_submitted_response(core, false);
+ });
+
+ it("award depending on current response throws error", async () => {
+ const doenetMLs = [
+ `$ans.currentResponse=5`,
+ `$ans.currentResponse1=5`,
+ `$ans.currentResponse2=5`,
+ `$ans.currentResponses=5`,
+ ];
+
+ for (let doenetML of doenetMLs) {
+ await expect(createTestCore({ doenetML })).rejects.toThrow(
+ "Circular dependency",
+ );
+ }
+ });
+
it("answer award with math", async () => {
const doenetML = `
diff --git a/packages/doenetml/package.json b/packages/doenetml/package.json
index 25c61d5d9..6c6044a65 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-alpha19",
+ "version": "0.7.0-alpha20",
"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 5a6f95dcd..94a1ff434 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-alpha19",
+ "version": "0.7.0-alpha20",
"license": "AGPL-3.0-or-later",
"homepage": "https://github.com/Doenet/DoenetML#readme",
"private": true,