From 135187f1f429334a9d82cd67529f59a2c3805f98 Mon Sep 17 00:00:00 2001 From: miaoye que Date: Tue, 1 Oct 2024 22:23:34 -0400 Subject: [PATCH] add test file for sketch verifier --- src/core/friendly_errors/sketch_verifier.js | 37 +++-- test/unit/core/sketch_overrides.js | 142 ++++++++++++++++++++ test/unit/spec.js | 1 + 3 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 test/unit/core/sketch_overrides.js diff --git a/src/core/friendly_errors/sketch_verifier.js b/src/core/friendly_errors/sketch_verifier.js index fe88ab9fda..c646360f81 100644 --- a/src/core/friendly_errors/sketch_verifier.js +++ b/src/core/friendly_errors/sketch_verifier.js @@ -37,6 +37,16 @@ function sketchVerifier(p5, fn) { return userCode; } + /** + * Extracts the user-defined variables and functions from the user code with + * the help of Espree parser. + * + * @method extractUserDefinedVariablesAndFuncs + * @param {string} codeStr - The code to extract variables and functions from. + * @returns {Object} An object containing the user's defined variables and functions. + * @returns {string[]} [userDefinitions.variables] Array of user-defined variable names. + * @returns {strings[]} [userDefinitions.functions] Array of user-defined function names. + */ fn.extractUserDefinedVariablesAndFuncs = function (codeStr) { const userDefinitions = { variables: [], @@ -53,23 +63,22 @@ function sketchVerifier(p5, fn) { }); function traverse(node) { - switch (node.type) { + const { type, declarations, id, init } = node; + + switch (type) { case 'VariableDeclaration': - node.declarations.forEach(declaration => { - if (declaration.id.type === 'Identifier') { - userDefinitions.variables.push(declaration.id.name); + declarations.forEach(({ id, init }) => { + if (id.type === 'Identifier') { + const category = init && ['ArrowFunctionExpression', 'FunctionExpression'].includes(init.type) + ? 'functions' + : 'variables'; + userDefinitions[category].push(id.name); } }); break; case 'FunctionDeclaration': - if (node.id && node.id.type === 'Identifier') { - userDefinitions.functions.push(node.id.name); - } - break; - case 'ArrowFunctionExpression': - case 'FunctionExpression': - if (node.parent && node.parent.type === 'VariableDeclarator') { - userDefinitions.functions.push(node.parent.id.name); + if (id?.type === 'Identifier') { + userDefinitions.functions.push(id.name); } break; } @@ -87,6 +96,7 @@ function sketchVerifier(p5, fn) { traverse(ast); } catch (error) { + // TODO: Replace this with a friendly error message. console.error('Error parsing code:', error); } @@ -96,7 +106,8 @@ function sketchVerifier(p5, fn) { fn.run = async function () { const userCode = await fn.getUserCode(); const userDefinedVariablesAndFuncs = fn.extractUserDefinedVariablesAndFuncs(userCode); - console.log(userDefinedVariablesAndFuncs); + + return userDefinedVariablesAndFuncs; } } diff --git a/test/unit/core/sketch_overrides.js b/test/unit/core/sketch_overrides.js new file mode 100644 index 0000000000..7c183ffd73 --- /dev/null +++ b/test/unit/core/sketch_overrides.js @@ -0,0 +1,142 @@ +import sketchVerifier from '../../../src/core/friendly_errors/sketch_verifier.js'; + +suite('Validate Params', function () { + const mockP5 = { + _validateParameters: vi.fn() + }; + const mockP5Prototype = {}; + + beforeAll(function () { + sketchVerifier(mockP5, mockP5Prototype); + }); + + afterAll(function () { + }); + + suite('fetchScript()', function () { + const url = 'https://www.p5test.com/sketch.js'; + const code = 'p.createCanvas(200, 200);'; + + test('Fetches script content from src', async function () { + const mockFetch = vi.fn(() => + Promise.resolve({ + text: () => Promise.resolve(code) + }) + ); + vi.stubGlobal('fetch', mockFetch); + + const mockScript = { src: url }; + const result = await mockP5Prototype.fetchScript(mockScript); + + expect(mockFetch).toHaveBeenCalledWith(url); + expect(result).toBe(code); + + vi.unstubAllGlobals(); + }); + + test('Fetches code when there is no src attribute', async function () { + const mockScript = { textContent: code }; + const result = await mockP5Prototype.fetchScript(mockScript); + + expect(result).toBe(code); + }); + }); + + suite('getUserCode()', function () { + const userCode = "let c = p5.Color(20, 20, 20);"; + + test('fetches the last script element', async function () { + document.body.innerHTML = ` + + + + `; + + mockP5Prototype.fetchScript = vi.fn(() => Promise.resolve(userCode)); + + const result = await mockP5Prototype.getUserCode(); + + expect(mockP5Prototype.fetchScript).toHaveBeenCalledTimes(1); + expect(result).toBe(userCode); + }); + }); + + suite('extractUserDefinedVariablesAndFuncs()', function () { + test('Extracts user-defined variables and functions', function () { + const code = ` + let x = 5; + const y = 10; + var z = 15; + let v1, v2, v3 + function foo() {} + const bar = () => {}; + const baz = (x) => x * 2; + `; + + const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(code); + + expect(result.variables).toEqual(['x', 'y', 'z', 'v1', 'v2', 'v3']); + expect(result.functions).toEqual(['foo', 'bar', 'baz']); + }); + + // Sketch verifier should ignore the following types of lines: + // - Comments (both single line and multi-line) + // - Function calls + // - Non-declaration code + test('Ignores other lines', function () { + const code = ` + // This is a comment + let x = 5; + /* This is a multi-line comment. + * This is a multi-line comment. + */ + const y = 10; + console.log("This is a statement"); + foo(5); + p5.Math.random(); + if (true) { + let z = 15; + } + for (let i = 0; i < 5; i++) {} + `; + + const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(code); + + expect(result.variables).toEqual(['x', 'y', 'z', 'i']); + expect(result.functions).toEqual([]); + }); + + test('Handles parsing errors', function () { + const invalidCode = 'let x = ;'; + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { }); + + const result = mockP5Prototype.extractUserDefinedVariablesAndFuncs(invalidCode); + + expect(consoleSpy).toHaveBeenCalled(); + expect(result).toEqual({ variables: [], functions: [] }); + + consoleSpy.mockRestore(); + }); + }); + + suite('run()', function () { + test('Returns extracted variables and functions', async function () { + const mockScript = ` + let x = 5; + const y = 10; + function foo() {} + const bar = () => {}; + `; + mockP5Prototype.getUserCode = vi.fn(() => Promise.resolve(mockScript)); + + const result = await mockP5Prototype.run(); + + expect(mockP5Prototype.getUserCode).toHaveBeenCalledTimes(1); + + expect(result).toEqual({ + variables: ['x', 'y'], + functions: ['foo', 'bar'] + }); + }); + }); +}); \ No newline at end of file diff --git a/test/unit/spec.js b/test/unit/spec.js index 995b152693..8df31317f2 100644 --- a/test/unit/spec.js +++ b/test/unit/spec.js @@ -13,6 +13,7 @@ var spec = { 'param_errors', 'preload', 'rendering', + 'sketch_overrides', 'structure', 'transform', 'version',