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