Skip to content
This repository has been archived by the owner on Sep 10, 2023. It is now read-only.

Commit

Permalink
test: add test case for scope
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy committed Mar 29, 2018
1 parent 178ac11 commit 256fcd8
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 9 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "Javascript Interpreter, run Javascript code in ECMAScript",
"main": "./build/src/vm.js",
"scripts": {
"test": "rm -rf ./build && tsc -p ./ && nyc ava ./build/test",
"test":
"rm -rf ./build && tsc -p ./ && nyc ava ./build/test ./build/src/**/*.test.js",
"build": "rm -rf ./build && tsc -p ./ -d",
"start": "npm run build && parcel ./doc/index.html -d vm.js",
"docs":
Expand Down
10 changes: 5 additions & 5 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface ISandBox {

import g from "./global";

const defaultContext: ISandBox = {
export const DEFAULT_CONTEXT: ISandBox = {
Array,
Boolean,
clearInterval,
Expand Down Expand Up @@ -41,13 +41,13 @@ const defaultContext: ISandBox = {
SyntaxError,
TypeError,
unescape,
URIError
URIError,
global: g
};

export default class Context {
public global: any = g;
export class Context {
constructor(externalContext: ISandBox = {}) {
const ctx = { ...defaultContext, ...externalContext };
const ctx = { ...DEFAULT_CONTEXT, ...externalContext };
for (const attr in ctx) {
if (ctx.hasOwnProperty(attr)) {
this[attr] = ctx[attr];
Expand Down
189 changes: 189 additions & 0 deletions src/scope.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import test from "ava";
import { Scope } from "./scope";
import { Context, DEFAULT_CONTEXT } from "./context";
import { ErrDuplicateDeclard } from "./error";

test("root scope", t => {
const scope = new Scope("root", null);
t.deepEqual(scope.type, "root");
t.deepEqual(scope.level, 0);
t.deepEqual(scope.length, 0);
t.deepEqual(scope.parent, null);
t.true(scope.isolated);
t.false(scope.invasive);
t.deepEqual(scope.raw, {});
t.deepEqual(scope.length, 0);
t.deepEqual(scope.origin, null);
});

test("setContext()", t => {
const scope = new Scope("root", null);
scope.setContext(new Context());

// default context
t.deepEqual(scope.raw, DEFAULT_CONTEXT);

scope.setContext(new Context({ name: "axetroy" }));

// context
t.deepEqual(scope.raw, { ...DEFAULT_CONTEXT, ...{ name: "axetroy" } });

t.true(!!scope.hasOwnBinding("name"));
});

test("hasOwnBinding()", t => {
const scope = new Scope("root", null);
scope.setContext(new Context());
t.true(!!scope.hasOwnBinding("console"));
});

test("hasBinding()", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm"));

const child = scope.createChild("block");

// can not found the var in the current scope
t.deepEqual(child.hasOwnBinding("name"), undefined);

// can found the var in the parent scope
t.true(!!child.hasBinding("name"));
});

test("var()", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm"));

const $var = scope.hasOwnBinding("name");

if (!$var) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");
});

test("'var' can be redeclare if variable have been declare with 'var'", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm"));

const $var = scope.hasOwnBinding("name");

if (!$var) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");

t.true(scope.var("name", "hello")); // redeclare

const $newVar = scope.hasOwnBinding("name");

if (!$newVar) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");
t.deepEqual($newVar.value, "hello");
});

test("let can be redeclare", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare

const $var = scope.hasOwnBinding("name");

if (!$var) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");

t.throws(() => {
scope.let("name", "hello"); // redeclare
}, ErrDuplicateDeclard("name").message);
});

test("const can be redeclare", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare

const $var = scope.hasOwnBinding("name");

if (!$var) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");

t.throws(() => {
scope.const("name", "hello"); // redeclare
}, ErrDuplicateDeclard("name").message);
});

test("delete variable from a scope", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare

t.deepEqual(scope.length, 1);

const $var = scope.hasOwnBinding("name");

if (!$var) {
return t.fail("Var should be found");
}

t.deepEqual($var.value, "vm");

t.true(scope.del("name"));

t.deepEqual(scope.hasOwnBinding("name"), undefined);
t.deepEqual(scope.length, 0);
});

test("create child", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare
t.deepEqual(scope.level, 0);
t.deepEqual(scope.length, 1);

const child = scope.createChild("block");
t.deepEqual(child.level, 1);
t.deepEqual(child.length, 0);

t.deepEqual(child.hasOwnBinding("name"), undefined);
t.true(!!child.hasBinding("name"));
});

test("fork child", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare
t.deepEqual(scope.level, 0);
t.deepEqual(scope.length, 1);
t.deepEqual(scope.raw, { name: "vm" });

const sibling = scope.fork("block");
t.deepEqual(sibling.level, 0);
t.deepEqual(sibling.length, 1);
t.deepEqual(sibling.raw, { name: "vm" });
t.true(sibling.origin === scope);

t.true(!!sibling.hasBinding("name"));
});

test("locate scope", t => {
const scope = new Scope("root", null);
t.true(scope.var("name", "vm")); // declare

const child = scope.createChild("block");

const childChild = child.createChild("block");

const target = childChild.locate("name");

if (!target) {
t.fail("Can not found the target scope");
}

t.true(target === scope);
});
19 changes: 17 additions & 2 deletions src/scope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Context from "./context";
import { Context } from "./context";
import { ErrDuplicateDeclard } from "./error";
import { Kind, ScopeType } from "./type";
import { Var } from "./var";
Expand Down Expand Up @@ -29,6 +29,20 @@ export class Scope {
this.context = new Context();
}

get length(): number {
return Object.keys(this.content).length;
}

get raw(): { [key: string]: any } {
const raw = {};
for (const attr in this.content) {
if (this.content.hasOwnProperty(attr)) {
raw[attr] = this.content[attr].value;
}
}
return raw;
}

/**
* Set context of a scope
* @param {Context} context
Expand Down Expand Up @@ -193,8 +207,9 @@ export class Scope {
* @param {string} varName
* @memberof Scope
*/
public del(varName: string) {
public del(varName: string): boolean {
delete this.content[varName];
return true;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/vm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse } from "babylon";
import Context, { ISandBox } from "./context";
import { Context, ISandBox } from "./context";
import evaluate from "./evaluate";
import { Path } from "./path";
import { Scope } from "./scope";
Expand Down

0 comments on commit 256fcd8

Please sign in to comment.