Skip to content

Commit

Permalink
add test file for sketch verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
sproutleaf committed Oct 2, 2024
1 parent 0a7cec8 commit 135187f
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 13 deletions.
37 changes: 24 additions & 13 deletions src/core/friendly_errors/sketch_verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand All @@ -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;
}
Expand All @@ -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);
}

Expand All @@ -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;
}
}

Expand Down
142 changes: 142 additions & 0 deletions test/unit/core/sketch_overrides.js
Original file line number Diff line number Diff line change
@@ -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 = `
<script src="p5.js"></script>
<script src="www.p5test.com/sketch.js"></script>
<script>let c = p5.Color(20, 20, 20);</script>
`;

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']
});
});
});
});
1 change: 1 addition & 0 deletions test/unit/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var spec = {
'param_errors',
'preload',
'rendering',
'sketch_overrides',
'structure',
'transform',
'version',
Expand Down

0 comments on commit 135187f

Please sign in to comment.