From be58646083e0482f1039a2cba2768f126d7ac05d Mon Sep 17 00:00:00 2001
From: Sarah Rietkerk <49178322+srietkerk@users.noreply.github.com>
Date: Tue, 13 Feb 2024 17:00:17 -0800
Subject: [PATCH 1/3] Teacher tool: first pass of nested validators (#9862)
* now sending all loaded validator plans in eval message
* added child plan check, refactored runValidatorPlanAsync to use for each loop to easily check children
* made rudimentary nested check, updated blocksexist validator to return successful blocks
* now passing in child blocks to child validator plans
* renamed check variable
* check if a child validator passes per possible parent, not the other way around
* renamed runvalidatorplanasync since it is no longer async
* chaned planBank to planLib
* fixed check passed bug, only getting the successful blocks list if blocks exist passed
---
common-docs/teachertool/catalog-shared.json | 12 +++
.../teachertool/validator-plans-shared.json | 27 ++++++
localtypings/pxteditor.d.ts | 1 +
localtypings/validatorPlan.d.ts | 1 +
pxteditor/code-validation/index.ts | 2 +-
pxteditor/code-validation/runValidatorPlan.ts | 84 +++++++++++++++++++
.../code-validation/runValidatorPlanAsync.ts | 67 ---------------
.../code-validation/validateBlocksExist.ts | 15 ++++
pxteditor/editorcontroller.ts | 5 +-
.../src/services/makecodeEditorService.ts | 4 +-
.../src/transforms/runEvaluateAsync.ts | 9 +-
teachertool/src/types/errorCode.ts | 1 +
12 files changed, 156 insertions(+), 72 deletions(-)
create mode 100644 pxteditor/code-validation/runValidatorPlan.ts
delete mode 100644 pxteditor/code-validation/runValidatorPlanAsync.ts
diff --git a/common-docs/teachertool/catalog-shared.json b/common-docs/teachertool/catalog-shared.json
index 508e4dd22c1d..2c73aca4bd65 100644
--- a/common-docs/teachertool/catalog-shared.json
+++ b/common-docs/teachertool/catalog-shared.json
@@ -35,6 +35,18 @@
"use": "variable_accessed",
"template": "At least one variable is accessed",
"docPath": "/teachertool"
+ },
+ {
+ "id": "8E6F9A92-2D22-4BB0-A77A-BD7490D3CEF7",
+ "use": "variable_set_random",
+ "template": "A custom variable's value is set to a random number",
+ "docPath": "/teachertool"
+ },
+ {
+ "id": "E8DAD360-F4AB-4E6B-9981-C14BDEE1295B",
+ "use": "device_random_used",
+ "template": "The math random number generator block is used",
+ "docPath": "/teachertool"
}
]
}
\ No newline at end of file
diff --git a/common-docs/teachertool/validator-plans-shared.json b/common-docs/teachertool/validator-plans-shared.json
index c01bd1b6271f..2a493460bc61 100644
--- a/common-docs/teachertool/validator-plans-shared.json
+++ b/common-docs/teachertool/validator-plans-shared.json
@@ -96,6 +96,33 @@
}
}
]
+ },
+ {
+ ".desc": "A custom variable's value is set to a random number",
+ "name": "variable_set_random",
+ "threshold": 1,
+ "checks": [
+ {
+ "validator": "blocksExist",
+ "blockCounts": {
+ "variables_set": 1
+ },
+ "childValidatorPlans": ["device_random_used"]
+ }
+ ]
+ },
+ {
+ ".desc": "Random number block used in project",
+ "name": "device_random_used",
+ "threshold": 1,
+ "checks": [
+ {
+ "validator": "blocksExist",
+ "blockCounts": {
+ "device_random": 1
+ }
+ }
+ ]
}
]
}
diff --git a/localtypings/pxteditor.d.ts b/localtypings/pxteditor.d.ts
index 01ece46f2965..a34eae36c407 100644
--- a/localtypings/pxteditor.d.ts
+++ b/localtypings/pxteditor.d.ts
@@ -298,6 +298,7 @@ declare namespace pxt.editor {
export interface EditorMessageRunEvalRequest extends EditorMessageRequest {
action: "runeval";
validatorPlan: pxt.blocks.ValidatorPlan;
+ planLib: pxt.blocks.ValidatorPlan[];
}
export interface EditorMessageRenderBlocksResponse {
diff --git a/localtypings/validatorPlan.d.ts b/localtypings/validatorPlan.d.ts
index 76ed715b496f..dec5e911a7a6 100644
--- a/localtypings/validatorPlan.d.ts
+++ b/localtypings/validatorPlan.d.ts
@@ -12,6 +12,7 @@ declare namespace pxt.blocks {
// Each type of validation will need to implement its own ValidatorCheck based on this.
export interface ValidatorCheckBase {
validator: string;
+ childValidatorPlans?: string[]
}
// Inputs for "Blocks Exist" validation.
diff --git a/pxteditor/code-validation/index.ts b/pxteditor/code-validation/index.ts
index a73d0f1a53ed..860a094aa15b 100644
--- a/pxteditor/code-validation/index.ts
+++ b/pxteditor/code-validation/index.ts
@@ -1,4 +1,4 @@
-export * from "./runValidatorPlanAsync";
+export * from "./runValidatorPlan";
export * from "./validateBlocksExist";
export * from "./validateBlocksInSetExist";
export * from "./validateCommentsExist";
diff --git a/pxteditor/code-validation/runValidatorPlan.ts b/pxteditor/code-validation/runValidatorPlan.ts
new file mode 100644
index 000000000000..17b4d1eb9479
--- /dev/null
+++ b/pxteditor/code-validation/runValidatorPlan.ts
@@ -0,0 +1,84 @@
+///
+
+import { validateBlocksExist } from "./validateBlocksExist";
+import { validateBlocksInSetExist } from "./validateBlocksInSetExist";
+import { validateBlockCommentsExist } from "./validateCommentsExist";
+import { validateSpecificBlockCommentsExist } from "./validateSpecificBlockCommentsExist";
+
+export function runValidatorPlan(usedBlocks: Blockly.Block[], plan: pxt.blocks.ValidatorPlan, planLib: pxt.blocks.ValidatorPlan[]): boolean {
+ const startTime = Date.now();
+ let checksSucceeded = 0;
+ let successfulBlocks: Blockly.Block[] = [];
+
+ for (const check of plan.checks) {
+ let checkPassed = false;
+ switch (check.validator) {
+ case "blocksExist":
+ [successfulBlocks, checkPassed] = [...runBlocksExistValidation(usedBlocks, check as pxt.blocks.BlocksExistValidatorCheck)];
+ break;
+ case "blockCommentsExist":
+ checkPassed = runValidateBlockCommentsExist(usedBlocks, check as pxt.blocks.BlockCommentsExistValidatorCheck);
+ break;
+ case "specificBlockCommentsExist":
+ checkPassed = runValidateSpecificBlockCommentsExist(usedBlocks, check as pxt.blocks.SpecificBlockCommentsExistValidatorCheck);
+ break;
+ case "blocksInSetExist":
+ [successfulBlocks, checkPassed] = [...runBlocksInSetExistValidation(usedBlocks, check as pxt.blocks.BlocksInSetExistValidatorCheck)];
+ break;
+ default:
+ pxt.debug(`Unrecognized validator: ${check.validator}`);
+ checkPassed = false;
+ break;
+ }
+
+ if (checkPassed && check.childValidatorPlans) {
+ for (const planName of check.childValidatorPlans) {
+ let timesPassed = 0;
+ for (const parentBlock of successfulBlocks) {
+ const blocksToUse = parentBlock.getChildren(true);
+ const childPlan = planLib.find((plan) => plan.name === planName);
+ const childPassed = runValidatorPlan(blocksToUse, childPlan, planLib);
+ timesPassed += childPassed ? 1 : 0;
+ }
+ checkPassed = checkPassed && timesPassed > 0;
+ }
+ }
+ checksSucceeded += checkPassed ? 1 : 0;
+ }
+
+ const passed = checksSucceeded >= plan.threshold;
+
+ pxt.tickEvent("validation.evaluation_complete", {
+ plan: plan.name,
+ durationMs: Date.now() - startTime,
+ passed: `${passed}`,
+ });
+
+ return passed;
+}
+
+function runBlocksExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksExistValidatorCheck): [Blockly.Block[], boolean] {
+ const blockResults = validateBlocksExist({ usedBlocks, requiredBlockCounts: inputs.blockCounts });
+ let successfulBlocks: Blockly.Block[] = [];
+ if (blockResults.passed) {
+ const blockIdsFromValidator = Object.keys(inputs.blockCounts)
+ const blockId = blockIdsFromValidator?.[0];
+ successfulBlocks = blockResults.successfulBlocks.length ? blockResults.successfulBlocks[0][blockId] : [];
+ }
+ return [successfulBlocks, blockResults.passed];
+}
+
+function runValidateBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlockCommentsExistValidatorCheck): boolean {
+ const blockResults = validateBlockCommentsExist({ usedBlocks, numRequired: inputs.count });
+ return blockResults.passed;
+}
+
+function runValidateSpecificBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.SpecificBlockCommentsExistValidatorCheck): boolean {
+ const blockResults = validateSpecificBlockCommentsExist({ usedBlocks, blockType: inputs.blockType });
+ return blockResults.passed;
+}
+
+function runBlocksInSetExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksInSetExistValidatorCheck): [Blockly.Block[], boolean] {
+ const blockResults = validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
+ return [blockResults.successfulBlocks, blockResults.passed];
+}
\ No newline at end of file
diff --git a/pxteditor/code-validation/runValidatorPlanAsync.ts b/pxteditor/code-validation/runValidatorPlanAsync.ts
deleted file mode 100644
index 347763cb2ee9..000000000000
--- a/pxteditor/code-validation/runValidatorPlanAsync.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-///
-
-import { validateBlocksExist } from "./validateBlocksExist";
-import { validateBlocksInSetExist } from "./validateBlocksInSetExist";
-import { validateBlockCommentsExist } from "./validateCommentsExist";
-import { validateSpecificBlockCommentsExist } from "./validateSpecificBlockCommentsExist";
-
-const maxConcurrentChecks = 4;
-
-export async function runValidatorPlanAsync(usedBlocks: Blockly.Block[], plan: pxt.blocks.ValidatorPlan): Promise {
- // Each plan can have multiple checks it needs to run.
- // Run all of them in parallel, and then check if the number of successes is greater than the specified threshold.
- // TBD if it's faster to run in parallel without short-circuiting once the threshold is reached, or if it's faster to run sequentially and short-circuit.
- const startTime = Date.now();
-
- const checkRuns = pxt.Util.promisePoolAsync(maxConcurrentChecks, plan.checks, async (check: pxt.blocks.ValidatorCheckBase): Promise => {
- switch (check.validator) {
- case "blocksExist":
- return runBlocksExistValidation(usedBlocks, check as pxt.blocks.BlocksExistValidatorCheck);
- case "blockCommentsExist":
- return runValidateBlockCommentsExist(usedBlocks, check as pxt.blocks.BlockCommentsExistValidatorCheck);
- case "specificBlockCommentsExist":
- return runValidateSpecificBlockCommentsExist(usedBlocks, check as pxt.blocks.SpecificBlockCommentsExistValidatorCheck);
- case "blocksInSetExist":
- return runBlocksInSetExistValidation(usedBlocks, check as pxt.blocks.BlocksInSetExistValidatorCheck);
- default:
- pxt.debug(`Unrecognized validator: ${check.validator}`);
- return false;
- }
- });
-
- const results = await checkRuns;
- const successCount = results.filter((r) => r).length;
- const passed = successCount >= plan.threshold;
-
- pxt.tickEvent("validation.evaluation_complete", {
- plan: plan.name,
- durationMs: Date.now() - startTime,
- passed: `${passed}`,
- });
-
- return passed;
-}
-
-function runBlocksExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksExistValidatorCheck): boolean {
- const blockResults = validateBlocksExist({ usedBlocks, requiredBlockCounts: inputs.blockCounts });
- const success =
- blockResults.disabledBlocks.length === 0 &&
- blockResults.missingBlocks.length === 0 &&
- blockResults.insufficientBlocks.length === 0;
- return success;
-}
-
-function runValidateBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlockCommentsExistValidatorCheck): boolean {
- const blockResults = validateBlockCommentsExist({ usedBlocks, numRequired: inputs.count });
- return blockResults.passed;
-}
-
-function runValidateSpecificBlockCommentsExist(usedBlocks: Blockly.Block[], inputs: pxt.blocks.SpecificBlockCommentsExistValidatorCheck): boolean {
- const blockResults = validateSpecificBlockCommentsExist({ usedBlocks, blockType: inputs.blockType });
- return blockResults.passed;
-}
-
-function runBlocksInSetExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksInSetExistValidatorCheck): boolean {
- const blockResults = validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
- return blockResults.passed;
-}
\ No newline at end of file
diff --git a/pxteditor/code-validation/validateBlocksExist.ts b/pxteditor/code-validation/validateBlocksExist.ts
index 7ade00313157..b368ab43d176 100644
--- a/pxteditor/code-validation/validateBlocksExist.ts
+++ b/pxteditor/code-validation/validateBlocksExist.ts
@@ -6,10 +6,13 @@ export function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
missingBlocks: string[],
disabledBlocks: string[],
insufficientBlocks: string[],
+ successfulBlocks: pxt.Map[],
+ passed: boolean
} {
let missingBlocks: string[] = [];
let disabledBlocks: string[] = [];
let insufficientBlocks: string[] = [];
+ let successfulBlocks: pxt.Map[] = [];
const userBlocksEnabledByType = usedBlocks?.reduce((acc: pxt.Map, block) => {
acc[block.type] = (acc[block.type] || 0) + (block.isEnabled() ? 1 : 0);
return acc;
@@ -17,6 +20,11 @@ export function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
for (const [requiredBlockId, requiredCount] of Object.entries(requiredBlockCounts || {})) {
const countForBlock = userBlocksEnabledByType[requiredBlockId];
+ const passedBlocks = usedBlocks.filter((block) => block.type === requiredBlockId);
+ if (passedBlocks.length > 0) {
+ successfulBlocks.push({ [requiredBlockId]: passedBlocks})
+ }
+
if (countForBlock === undefined) {
// user did not use a specific block
missingBlocks.push(requiredBlockId);
@@ -29,9 +37,16 @@ export function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
}
}
+ const passed =
+ missingBlocks.length === 0 &&
+ disabledBlocks.length === 0 &&
+ insufficientBlocks.length === 0;
+
return {
missingBlocks,
disabledBlocks,
insufficientBlocks,
+ successfulBlocks,
+ passed
}
}
\ No newline at end of file
diff --git a/pxteditor/editorcontroller.ts b/pxteditor/editorcontroller.ts
index e42937e7ad1b..019e1c27f232 100644
--- a/pxteditor/editorcontroller.ts
+++ b/pxteditor/editorcontroller.ts
@@ -1,6 +1,6 @@
///
-import { runValidatorPlanAsync } from "./code-validation/runValidatorPlanAsync";
+import { runValidatorPlan } from "./code-validation/runValidatorPlan";
import IProjectView = pxt.editor.IProjectView;
const pendingRequests: pxt.Map<{
@@ -157,10 +157,11 @@ export function bindEditorMessages(getEditorAsync: () => Promise)
case "runeval": {
const evalmsg = data as pxt.editor.EditorMessageRunEvalRequest;
const plan = evalmsg.validatorPlan;
+ const planLib = evalmsg.planLib;
return Promise.resolve()
.then(() => {
const blocks = projectView.getBlocks();
- return runValidatorPlanAsync(blocks, plan)})
+ return runValidatorPlan(blocks, plan, planLib)})
.then (results => {
resp = { result: results };
});
diff --git a/teachertool/src/services/makecodeEditorService.ts b/teachertool/src/services/makecodeEditorService.ts
index 7333453bb2a4..922837077145 100644
--- a/teachertool/src/services/makecodeEditorService.ts
+++ b/teachertool/src/services/makecodeEditorService.ts
@@ -92,7 +92,8 @@ export async function setHighContrastAsync(on: boolean) {
}
export async function runValidatorPlanAsync(
- validatorPlan: pxt.blocks.ValidatorPlan
+ validatorPlan: pxt.blocks.ValidatorPlan,
+ planLib: pxt.blocks.ValidatorPlan[]
): Promise {
let evalResults = undefined;
@@ -101,6 +102,7 @@ export async function runValidatorPlanAsync(
type: "pxteditor",
action: "runeval",
validatorPlan: validatorPlan,
+ planLib: planLib
} as pxt.editor.EditorMessageRunEvalRequest);
const result = response as pxt.editor.EditorMessageResponse;
validateResponse(result, true); // Throws on failure
diff --git a/teachertool/src/transforms/runEvaluateAsync.ts b/teachertool/src/transforms/runEvaluateAsync.ts
index 58af8a612ddf..8d802d52fab0 100644
--- a/teachertool/src/transforms/runEvaluateAsync.ts
+++ b/teachertool/src/transforms/runEvaluateAsync.ts
@@ -50,6 +50,13 @@ export async function runEvaluateAsync(fromUserInteraction: boolean) {
new Promise(async resolve => {
dispatch(Actions.setEvalResult(criteriaInstance.instanceId, CriteriaEvaluationResult.InProgress));
+ const loadedValidatorPlans = teacherTool.validatorPlans;
+ if (!loadedValidatorPlans) {
+ logError(ErrorCode.validatorPlansNotFound, "Attempting to evaluate criteria without any plans");
+ dispatch(Actions.clearEvalResult(criteriaInstance.instanceId));
+ return resolve(false);
+ }
+
const plan = generateValidatorPlan(criteriaInstance);
if (!plan) {
@@ -57,7 +64,7 @@ export async function runEvaluateAsync(fromUserInteraction: boolean) {
return resolve(false);
}
- const planResult = await runValidatorPlanAsync(plan);
+ const planResult = await runValidatorPlanAsync(plan, loadedValidatorPlans);
if (planResult) {
dispatch(
diff --git a/teachertool/src/types/errorCode.ts b/teachertool/src/types/errorCode.ts
index 4b310a41def9..5760a4549873 100644
--- a/teachertool/src/types/errorCode.ts
+++ b/teachertool/src/types/errorCode.ts
@@ -15,4 +15,5 @@ export enum ErrorCode {
unableToReadRubricFile = "unableToReadRubricFile",
localStorageReadError = "localStorageReadError",
localStorageWriteError = "localStorageWriteError",
+ validatorPlansNotFound = "validatorPlansNotFound"
}
From d2e0ba55500bb8f2e27e41d80f3f834d08369cbc Mon Sep 17 00:00:00 2001
From: Sarah Rietkerk <49178322+srietkerk@users.noreply.github.com>
Date: Tue, 13 Feb 2024 17:01:11 -0800
Subject: [PATCH 2/3] specific comments exists now fails if no specific blocks
exist (#9866)
---
.../code-validation/validateSpecificBlockCommentsExist.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pxteditor/code-validation/validateSpecificBlockCommentsExist.ts b/pxteditor/code-validation/validateSpecificBlockCommentsExist.ts
index d044c1a3281b..377b1a44e733 100644
--- a/pxteditor/code-validation/validateSpecificBlockCommentsExist.ts
+++ b/pxteditor/code-validation/validateSpecificBlockCommentsExist.ts
@@ -9,5 +9,6 @@ export function validateSpecificBlockCommentsExist({ usedBlocks, blockType }: {
} {
const allSpecifcBlocks = usedBlocks.filter((block) => block.type === blockType);
const uncommentedBlocks = allSpecifcBlocks.filter((block) => !block.getCommentText());
- return { uncommentedBlocks, passed: uncommentedBlocks.length === 0 };
+ const passed = allSpecifcBlocks.length === 0 ? false : uncommentedBlocks.length === 0;
+ return { uncommentedBlocks, passed };
}
\ No newline at end of file
From 461a8db0892c9023595d1d745ac86b16bb5a6ca6 Mon Sep 17 00:00:00 2001
From: Sarah Rietkerk <49178322+srietkerk@users.noreply.github.com>
Date: Wed, 14 Feb 2024 16:34:06 -0800
Subject: [PATCH 3/3] Teacher tool: field value validator added (#9867)
* field value validator added
* change template string for num equality validator
Co-authored-by: Eric Anderson
---------
Co-authored-by: Eric Anderson
---
common-docs/teachertool/catalog-shared.json | 6 +++++
.../teachertool/validator-plans-shared.json | 27 +++++++++++++++++++
localtypings/validatorPlan.d.ts | 7 +++++
pxteditor/code-validation/runValidatorPlan.ts | 14 ++++++++++
.../validateBlockFieldValueExists.ts | 21 +++++++++++++++
5 files changed, 75 insertions(+)
create mode 100644 pxteditor/code-validation/validateBlockFieldValueExists.ts
diff --git a/common-docs/teachertool/catalog-shared.json b/common-docs/teachertool/catalog-shared.json
index 2c73aca4bd65..cab05f47fcf8 100644
--- a/common-docs/teachertool/catalog-shared.json
+++ b/common-docs/teachertool/catalog-shared.json
@@ -47,6 +47,12 @@
"use": "device_random_used",
"template": "The math random number generator block is used",
"docPath": "/teachertool"
+ },
+ {
+ "id": "DC564060-F177-46CC-A3AC-890CD8545972",
+ "use": "num_compare_eq",
+ "template": "Compare two numbers for equality",
+ "docPath": "/teachertool"
}
]
}
\ No newline at end of file
diff --git a/common-docs/teachertool/validator-plans-shared.json b/common-docs/teachertool/validator-plans-shared.json
index 2a493460bc61..0d0d06a40d84 100644
--- a/common-docs/teachertool/validator-plans-shared.json
+++ b/common-docs/teachertool/validator-plans-shared.json
@@ -123,6 +123,33 @@
}
}
]
+ },
+ {
+ ".desc": "make sure two numbers are equal",
+ "name": "num_compare_eq",
+ "threshold": 1,
+ "checks": [
+ {
+ "validator": "blockFieldValueExists",
+ "fieldType": "OP",
+ "fieldValue": "EQ",
+ "blockType": "logic_compare",
+ "childValidatorPlans": ["two_nums_exist"]
+ }
+ ]
+ },
+ {
+ ".desc": "two numbers exist",
+ "name": "two_nums_exist",
+ "threshold": 1,
+ "checks": [
+ {
+ "validator": "blocksExist",
+ "blockCounts": {
+ "math_number": 2
+ }
+ }
+ ]
}
]
}
diff --git a/localtypings/validatorPlan.d.ts b/localtypings/validatorPlan.d.ts
index dec5e911a7a6..53d683cfc9f0 100644
--- a/localtypings/validatorPlan.d.ts
+++ b/localtypings/validatorPlan.d.ts
@@ -39,4 +39,11 @@ declare namespace pxt.blocks {
export interface EvaluationResult {
result: boolean;
}
+
+ export interface BlockFieldValueExistsCheck extends ValidatorCheckBase {
+ validator: "blockFieldValueExists";
+ fieldType: string;
+ fieldValue: string;
+ blockType: string;
+ }
}
\ No newline at end of file
diff --git a/pxteditor/code-validation/runValidatorPlan.ts b/pxteditor/code-validation/runValidatorPlan.ts
index 17b4d1eb9479..5deedbf35162 100644
--- a/pxteditor/code-validation/runValidatorPlan.ts
+++ b/pxteditor/code-validation/runValidatorPlan.ts
@@ -1,5 +1,6 @@
///
+import { validateBlockFieldValueExists } from "./validateBlockFieldValueExists";
import { validateBlocksExist } from "./validateBlocksExist";
import { validateBlocksInSetExist } from "./validateBlocksInSetExist";
import { validateBlockCommentsExist } from "./validateCommentsExist";
@@ -25,6 +26,9 @@ export function runValidatorPlan(usedBlocks: Blockly.Block[], plan: pxt.blocks.V
case "blocksInSetExist":
[successfulBlocks, checkPassed] = [...runBlocksInSetExistValidation(usedBlocks, check as pxt.blocks.BlocksInSetExistValidatorCheck)];
break;
+ case "blockFieldValueExists":
+ [successfulBlocks, checkPassed] = [...runBlockFieldValueExistsValidation(usedBlocks, check as pxt.blocks.BlockFieldValueExistsCheck)];
+ break;
default:
pxt.debug(`Unrecognized validator: ${check.validator}`);
checkPassed = false;
@@ -81,4 +85,14 @@ function runValidateSpecificBlockCommentsExist(usedBlocks: Blockly.Block[], inpu
function runBlocksInSetExistValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlocksInSetExistValidatorCheck): [Blockly.Block[], boolean] {
const blockResults = validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
return [blockResults.successfulBlocks, blockResults.passed];
+}
+
+function runBlockFieldValueExistsValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.BlockFieldValueExistsCheck): [Blockly.Block[], boolean] {
+ const blockResults = validateBlockFieldValueExists({
+ usedBlocks,
+ fieldType: inputs.fieldType,
+ fieldValue: inputs.fieldValue,
+ specifiedBlock: inputs.blockType
+ });
+ return [blockResults.successfulBlocks, blockResults.passed];
}
\ No newline at end of file
diff --git a/pxteditor/code-validation/validateBlockFieldValueExists.ts b/pxteditor/code-validation/validateBlockFieldValueExists.ts
new file mode 100644
index 000000000000..a6a50c294d15
--- /dev/null
+++ b/pxteditor/code-validation/validateBlockFieldValueExists.ts
@@ -0,0 +1,21 @@
+// validates that one or more blocks comments are in the project
+// returns the blocks that have comments for teacher tool scenario
+export function validateBlockFieldValueExists({ usedBlocks, fieldType, fieldValue, specifiedBlock }: {
+ usedBlocks: Blockly.Block[],
+ fieldType: string,
+ fieldValue: string,
+ specifiedBlock: string,
+}): {
+ successfulBlocks: Blockly.Block[],
+ passed: boolean
+} {
+ const enabledSpecifiedBlocks = usedBlocks.filter((block) =>
+ block.isEnabled() && block.type === specifiedBlock
+ );
+
+ const successfulBlocks = enabledSpecifiedBlocks.filter((block) =>
+ block.getFieldValue(fieldType) === fieldValue
+ );
+
+ return { successfulBlocks, passed: successfulBlocks.length > 0 };
+}
\ No newline at end of file