({ testCase, testContainer, inputAce, outputAce })
| 187 | } |
| 188 | |
| 189 | addBlockly ({ testCase, testContainer, inputAce, outputAce }) { |
| 190 | // Initialize Blockly |
| 191 | const testBlockly = { loaded: false, loading: false, div: testContainer.find('.blockly-container')[0] } |
| 192 | testBlockly.load = () => { |
| 193 | const toolbox = blocklyUtils.createBlocklyToolbox({ propertyEntryGroups, codeLanguage: testCase.codeLanguage, codeFormat: 'blocks-text' }) |
| 194 | const blocklyOptions = blocklyUtils.createBlocklyOptions({ toolbox, codeLanguage: testCase.codeLanguage, codeFormat: 'blocks-text', renderer: 'thrasos', product: 'codecombat' }) |
| 195 | testBlockly.workspace = Blockly.inject(testBlockly.div, blocklyOptions) |
| 196 | this.blocklyWorkspaces.push(testBlockly.workspace) |
| 197 | testBlockly.loading = true |
| 198 | testBlockly.workspace.addChangeListener((event) => { |
| 199 | if (event.type === Blockly.Events.FINISHED_LOADING) { |
| 200 | testBlockly.loading = false |
| 201 | testBlockly.loaded = true |
| 202 | } else if (testBlockly.loading) { |
| 203 | return |
| 204 | } else if (!blocklyUtils.blocklyMutationEvents.includes(event.type)) { |
| 205 | return |
| 206 | } |
| 207 | // Blockly -> output ace |
| 208 | // TODO: make sure it's the kind of change we want to do, we don't fire multiple changes for same source? |
| 209 | const { blocklyState, blocklySource } = blocklyUtils.getBlocklySource(testBlockly.workspace, { codeLanguage: testCase.codeLanguage, product: 'codecombat' }) |
| 210 | console.log('New blockly state for', testCase.name, 'is', blocklyState) |
| 211 | outputAce.setValue(blocklySource) |
| 212 | outputAce.clearSelection() |
| 213 | const inputSource = inputAce.getValue() |
| 214 | if (inputSource === testCase.code) { |
| 215 | let status = 'success' |
| 216 | let reason = 'matched' |
| 217 | const normalizeCode = (s) => { |
| 218 | return s |
| 219 | .replace(/"/g, '\'') // Treat single and double quotes the same |
| 220 | .replace(/\\'/g, '\'') // Ignore escapes of single quotes, from above step |
| 221 | .replace(/^(let|const) /gm, 'var ') // Treat var, let, and const the same |
| 222 | .replace(/(\n *\n *)(\n *)+/g, '$1') // Ignore more than 2 newlines in a row |
| 223 | .replace(/!==/g, '!=') // Ignore !== vs. != |
| 224 | .replace(/===/g, '==') // Ignore !== vs. != |
| 225 | .replace(/ +(\/\/|#|--) .*?[Δ∆].*$/gm, '') // Ignore in-line comments with deltas |
| 226 | .replace(/;\n/g, '\n') // Ignore trailing semicolons |
| 227 | .replace(/;\n +(\/\/|#|--).*?$/gm, '\n') // Ignore trailing semicolons before comments |
| 228 | .replace(/;$/g, '') // Ignore trailing semicolons at end of program |
| 229 | .replace(/(while|if|for|function) \(/g, '$1(') // Ignore space between keyword and opening parenthesis |
| 230 | .replace(/( *(\/\/|#|--).*?) +$/gm, '$1') // Ignore trailing spaces after code comments |
| 231 | .trim() |
| 232 | } |
| 233 | // console.log('--------------------\n', normalizeCode(blocklySource), '\n================\n', normalizeCode(testCase.code), '\n++++++++++++++++++++++++') |
| 234 | if (normalizeCode(blocklySource) !== normalizeCode(testCase.code)) { |
| 235 | status = 'failure' |
| 236 | reason = 'code-mismatch' |
| 237 | } else if (blocklyUtils.blocklyStateIncludesBlockType(blocklyState, 'raw_code') || blocklyUtils.blocklyStateIncludesBlockType(blocklyState, 'raw_code_value')) { |
| 238 | status = 'failure' |
| 239 | reason = 'raw-code' |
| 240 | } |
| 241 | const result = { state: blocklyState, inputState: testCase.inputBlocklyState, outputCode: blocklySource, status, reason } |
| 242 | storage.save(`blockly-test-from-code_${testCase.key}_${hashString(testCase.code)}`, result) |
| 243 | testCase.status = status |
| 244 | testCase.reason = reason |
| 245 | testContainer.addClass(testCase.status) |
| 246 | testContainer.find('.test-status').text(`${testCase.status} - ${testCase.reason}`) // TODO: dedupe |
no test coverage detected