refactor: switch Context <-> Scope concepts

This commit is contained in:
harttle
2019-03-25 10:36:23 +08:00
parent 76345a64c3
commit 45e3c2bb8e
34 changed files with 272 additions and 272 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
import Liquid from '../../../../src/liquid'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
import { Context } from '../../../../src/scope/context'
import { Scope } from '../../../../src/context/scope'
use(chaiAsPromised)
describe('tags/for', function () {
let liquid: Liquid, ctx: Context
let liquid: Liquid, ctx: Scope
before(function () {
liquid = new Liquid()
liquid.registerTag('throwingTag', {
@@ -1,13 +1,13 @@
import * as chai from 'chai'
import Scope from '../../../src/scope/scope'
import { Context } from '../../../src/scope/context'
import Context from '../../../src/context/context'
import { Scope } from '../../../src/context/scope'
const expect = chai.expect
describe('scope', function () {
let scope: Scope, ctx: Context
let ctx: Context, scope: Scope
beforeEach(function () {
ctx = {
scope = {
foo: 'zoo',
one: 1,
zoo: { size: 4 },
@@ -17,76 +17,76 @@ describe('scope', function () {
arr: ['a', 'b']
}
}
scope = new Scope(ctx)
ctx = new Context(scope)
})
describe('#propertyAccessSeq()', function () {
it('should handle dot syntax', async function () {
expect(await scope.propertyAccessSeq('foo.bar'))
expect(await ctx.propertyAccessSeq('foo.bar'))
.to.deep.equal(['foo', 'bar'])
})
it('should handle [<String>] syntax', async function () {
expect(await scope.propertyAccessSeq('foo["bar"]'))
expect(await ctx.propertyAccessSeq('foo["bar"]'))
.to.deep.equal(['foo', 'bar'])
})
it('should handle [<Identifier>] syntax', async function () {
expect(await scope.propertyAccessSeq('foo[foo]'))
expect(await ctx.propertyAccessSeq('foo[foo]'))
.to.deep.equal(['foo', 'zoo'])
})
it('should handle nested access 1', async function () {
expect(await scope.propertyAccessSeq('foo[bar.zoo]'))
expect(await ctx.propertyAccessSeq('foo[bar.zoo]'))
.to.deep.equal(['foo', 'coo'])
})
it('should handle nested access 2', async function () {
expect(await scope.propertyAccessSeq('foo[bar["zoo"]]'))
expect(await ctx.propertyAccessSeq('foo[bar["zoo"]]'))
.to.deep.equal(['foo', 'coo'])
})
it('should handle nested access 3', async function () {
expect(await scope.propertyAccessSeq('bar["foo"].zoo'))
expect(await ctx.propertyAccessSeq('bar["foo"].zoo'))
.to.deep.equal(['bar', 'foo', 'zoo'])
})
it('should handle nested access 4', async function () {
expect(await scope.propertyAccessSeq('foo[0].bar'))
expect(await ctx.propertyAccessSeq('foo[0].bar'))
.to.deep.equal(['foo', '0', 'bar'])
})
it('should handle nested access 5', async function () {
expect(await scope.propertyAccessSeq('foo[one].bar'))
expect(await ctx.propertyAccessSeq('foo[one].bar'))
.to.deep.equal(['foo', '1', 'bar'])
})
it('should handle nested access 6', async function () {
expect(await scope.propertyAccessSeq('foo[two].bar'))
expect(await ctx.propertyAccessSeq('foo[two].bar'))
.to.deep.equal(['foo', 'undefined', 'bar'])
})
})
describe('#get()', function () {
it('should get direct property', async function () {
expect(await await scope.get('foo')).equal('zoo')
expect(await await ctx.get('foo')).equal('zoo')
})
it('undefined property should yield undefined', async function () {
expect(scope.get('notdefined')).to.be.rejected
expect(await scope.get('notdefined')).to.equal(undefined)
expect(await scope.get(false as any)).to.equal(undefined)
expect(ctx.get('notdefined')).to.be.rejected
expect(await ctx.get('notdefined')).to.equal(undefined)
expect(await ctx.get(false as any)).to.equal(undefined)
})
it('should throw for invalid path', async function () {
expect(scope.get('')).to.be.rejectedWith('invalid path:""')
expect(ctx.get('')).to.be.rejectedWith('invalid path:""')
})
it('should throw when [] unbalanced', async function () {
expect(scope.get('foo[bar')).to.be.rejectedWith(/unbalanced \[\]/)
expect(ctx.get('foo[bar')).to.be.rejectedWith(/unbalanced \[\]/)
})
it('should throw when "" unbalanced', async function () {
expect(scope.get('foo["bar]')).to.be.rejectedWith(/unbalanced "/)
expect(ctx.get('foo["bar]')).to.be.rejectedWith(/unbalanced "/)
})
it("should throw when '' unbalanced", async function () {
expect(scope.get("foo['bar]")).to.be.rejectedWith(/unbalanced '/)
expect(ctx.get("foo['bar]")).to.be.rejectedWith(/unbalanced '/)
})
it('should respect to toLiquid', async function () {
const scope = new Scope({ foo: {
const scope = new Context({ foo: {
toLiquid: () => ({ bar: 'BAR' }),
bar: 'bar'
} })
@@ -94,94 +94,94 @@ describe('scope', function () {
})
it('should access child property via dot syntax', async function () {
expect(await scope.get('bar.zoo')).to.equal('coo')
expect(await scope.get('bar.arr')).to.deep.equal(['a', 'b'])
expect(await ctx.get('bar.zoo')).to.equal('coo')
expect(await ctx.get('bar.arr')).to.deep.equal(['a', 'b'])
})
it('should access child property via [<String>] syntax', async function () {
expect(await scope.get('bar["zoo"]')).to.equal('coo')
expect(await ctx.get('bar["zoo"]')).to.equal('coo')
})
it('should access child property via [<Number>] syntax', async function () {
expect(await scope.get('bar.arr[0]')).to.equal('a')
expect(await ctx.get('bar.arr[0]')).to.equal('a')
})
it('should access child property via [<Identifier>] syntax', async function () {
expect(await scope.get('bar[foo]')).to.equal('coo')
expect(await ctx.get('bar[foo]')).to.equal('coo')
})
it('should return undefined when not exist', async function () {
expect(await scope.get('foo.foo.foo')).to.be.undefined
expect(await ctx.get('foo.foo.foo')).to.be.undefined
})
it('should return string length as size', async function () {
expect(await scope.get('foo.size')).to.equal(3)
expect(await ctx.get('foo.size')).to.equal(3)
})
it('should return array length as size', async function () {
expect(await scope.get('bar.arr.size')).to.equal(2)
expect(await ctx.get('bar.arr.size')).to.equal(2)
})
it('should return size property if exists', async function () {
expect(await scope.get('zoo.size')).to.equal(4)
expect(await ctx.get('zoo.size')).to.equal(4)
})
it('should return undefined if do not have size and length', async function () {
expect(await scope.get('one.size')).to.equal(undefined)
expect(await ctx.get('one.size')).to.equal(undefined)
})
})
describe('strictVariables', async function () {
let scope: Scope
let ctx: Context
beforeEach(function () {
scope = new Scope(ctx, {
ctx = new Context(ctx, {
strictVariables: true
} as any)
})
it('should throw when variable not defined', function () {
return expect(scope.get('notdefined')).to.be.rejectedWith(/undefined variable: notdefined/)
return expect(ctx.get('notdefined')).to.be.rejectedWith(/undefined variable: notdefined/)
})
it('should throw when deep variable not exist', async function () {
scope.contexts.push({ 'foo': 'FOO' })
return expect(scope.get('foo.bar.not.defined')).to.be.rejectedWith(/undefined variable: bar/)
ctx.scopes.push({ 'foo': 'FOO' })
return expect(ctx.get('foo.bar.not.defined')).to.be.rejectedWith(/undefined variable: bar/)
})
it('should throw when itself not defined', async function () {
scope.contexts.push({ 'foo': 'FOO' })
return expect(scope.get('foo.BAR')).to.be.rejectedWith(/undefined variable: BAR/)
ctx.scopes.push({ 'foo': 'FOO' })
return expect(ctx.get('foo.BAR')).to.be.rejectedWith(/undefined variable: BAR/)
})
it('should find variable in parent scope', async function () {
scope.contexts.push({ 'foo': 'foo' })
scope.push({
ctx.scopes.push({ 'foo': 'foo' })
ctx.push({
'bar': 'bar'
})
expect(await scope.get('foo')).to.equal('foo')
expect(await ctx.get('foo')).to.equal('foo')
})
})
describe('.getAll()', function () {
it('should get all properties when arguments empty', async function () {
expect(await scope.getAll()).deep.equal(ctx)
expect(await ctx.getAll()).deep.equal(scope)
})
})
describe('.push()', function () {
it('should push scope', async function () {
scope.contexts.push({ 'bar': 'bar' })
scope.push({
ctx.scopes.push({ 'bar': 'bar' })
ctx.push({
foo: 'foo'
})
expect(await scope.get('foo')).to.equal('foo')
expect(await scope.get('bar')).to.equal('bar')
expect(await ctx.get('foo')).to.equal('foo')
expect(await ctx.get('bar')).to.equal('bar')
})
it('should hide deep properties by push', async function () {
scope.contexts.push({ 'bar': { bar: 'bar' } })
scope.push({ bar: { foo: 'foo' } })
expect(await scope.get('bar.foo')).to.equal('foo')
expect(await scope.get('bar.bar')).to.equal(undefined)
ctx.scopes.push({ 'bar': { bar: 'bar' } })
ctx.push({ bar: { foo: 'foo' } })
expect(await ctx.get('bar.foo')).to.equal('foo')
expect(await ctx.get('bar.bar')).to.equal(undefined)
})
})
describe('.pop()', function () {
it('should pop scope', async function () {
scope.push({
ctx.push({
foo: 'foo'
})
scope.pop()
expect(await scope.get('foo')).to.equal('zoo')
ctx.pop()
expect(await ctx.get('foo')).to.equal('zoo')
})
})
it('should pop specified scope', async function () {
@@ -191,18 +191,18 @@ describe('scope', function () {
const scope2 = {
bar: 'bar'
}
scope.push(scope1)
scope.push(scope2)
expect(await scope.get('foo')).to.equal('foo')
expect(await scope.get('bar')).to.equal('bar')
scope.pop(scope1)
expect(await scope.get('foo')).to.equal('zoo')
expect(await scope.get('bar')).to.equal('bar')
ctx.push(scope1)
ctx.push(scope2)
expect(await ctx.get('foo')).to.equal('foo')
expect(await ctx.get('bar')).to.equal('bar')
ctx.pop(scope1)
expect(await ctx.get('foo')).to.equal('zoo')
expect(await ctx.get('bar')).to.equal('bar')
})
it('should throw when specified scope not found', function () {
const scope1 = {
foo: 'foo'
}
expect(() => scope.pop(scope1)).to.throw('scope not found, cannot pop')
expect(() => ctx.pop(scope1)).to.throw('scope not found, cannot pop')
})
})
+2 -2
View File
@@ -1,5 +1,5 @@
import { expect } from 'chai'
import Scope from '../../../src/scope/scope'
import Context from '../../../src/context/context'
import Token from '../../../src/parser/token'
import Tag from '../../../src/template/tag/tag'
import { Filter } from '../../../src/template/filter/filter'
@@ -20,7 +20,7 @@ describe('render', function () {
})
it('should render html', async function () {
const scope = new Scope()
const scope = new Context()
const token = { type: 'html', value: '<p>' } as Token
const html = await render.renderTemplates([new HTML(token)], scope)
return expect(html).to.equal('<p>')
+34 -34
View File
@@ -1,12 +1,12 @@
import Scope from '../../../src/scope/scope'
import Context from '../../../src/context/context'
import { expect } from 'chai'
import { evalExp, evalValue, isTruthy } from '../../../src/render/syntax'
describe('render/syntax', function () {
let scope: Scope
let ctx: Context
beforeEach(function () {
scope = new Scope({
ctx = new Context({
one: 1,
two: 2,
empty: '',
@@ -19,30 +19,30 @@ describe('render/syntax', function () {
describe('.evalValue()', function () {
it('should eval boolean literal', async function () {
expect(await evalValue('true', scope)).to.equal(true)
expect(await evalValue('TrUE', scope)).to.equal(undefined)
expect(await evalValue('false', scope)).to.equal(false)
expect(await evalValue('true', ctx)).to.equal(true)
expect(await evalValue('TrUE', ctx)).to.equal(undefined)
expect(await evalValue('false', ctx)).to.equal(false)
})
it('should eval number literal', async function () {
expect(await evalValue('2.3', scope)).to.equal(2.3)
expect(await evalValue('.32', scope)).to.equal(0.32)
expect(await evalValue('-23.', scope)).to.equal(-23)
expect(await evalValue('23', scope)).to.equal(23)
expect(await evalValue('2.3', ctx)).to.equal(2.3)
expect(await evalValue('.32', ctx)).to.equal(0.32)
expect(await evalValue('-23.', ctx)).to.equal(-23)
expect(await evalValue('23', ctx)).to.equal(23)
})
it('should eval string literal', async function () {
expect(await evalValue('"ab\'c"', scope)).to.equal("ab'c")
expect(await evalValue("'ab\"c'", scope)).to.equal('ab"c')
expect(await evalValue('"ab\'c"', ctx)).to.equal("ab'c")
expect(await evalValue("'ab\"c'", ctx)).to.equal('ab"c')
})
it('should eval nil literal', async function () {
expect(await evalValue('nil', scope)).to.be.null
expect(await evalValue('nil', ctx)).to.be.null
})
it('should eval null literal', async function () {
expect(await evalValue('null', scope)).to.be.null
expect(await evalValue('null', ctx)).to.be.null
})
it('should eval scope variables', async function () {
expect(await evalValue('one', scope)).to.equal(1)
expect(await evalValue('has_value?', scope)).to.equal(true)
expect(await evalValue('x', scope)).to.equal('XXX')
expect(await evalValue('one', ctx)).to.equal(1)
expect(await evalValue('has_value?', ctx)).to.equal(true)
expect(await evalValue('x', ctx)).to.equal('XXX')
})
})
@@ -66,37 +66,37 @@ describe('render/syntax', function () {
})
it('should eval simple expression', async function () {
expect(await evalExp('1<2', scope)).to.equal(true)
expect(await evalExp('2<=2', scope)).to.equal(true)
expect(await evalExp('one<=two', scope)).to.equal(true)
expect(await evalExp('x contains "x"', scope)).to.equal(false)
expect(await evalExp('x contains "X"', scope)).to.equal(true)
expect(await evalExp('1 contains "x"', scope)).to.equal(false)
expect(await evalExp('y contains "x"', scope)).to.equal(false)
expect(await evalExp('z contains "x"', scope)).to.equal(false)
expect(await evalExp('(1..5) contains 3', scope)).to.equal(true)
expect(await evalExp('(1..5) contains 6', scope)).to.equal(false)
expect(await evalExp('"<=" == "<="', scope)).to.equal(true)
expect(await evalExp('1<2', ctx)).to.equal(true)
expect(await evalExp('2<=2', ctx)).to.equal(true)
expect(await evalExp('one<=two', ctx)).to.equal(true)
expect(await evalExp('x contains "x"', ctx)).to.equal(false)
expect(await evalExp('x contains "X"', ctx)).to.equal(true)
expect(await evalExp('1 contains "x"', ctx)).to.equal(false)
expect(await evalExp('y contains "x"', ctx)).to.equal(false)
expect(await evalExp('z contains "x"', ctx)).to.equal(false)
expect(await evalExp('(1..5) contains 3', ctx)).to.equal(true)
expect(await evalExp('(1..5) contains 6', ctx)).to.equal(false)
expect(await evalExp('"<=" == "<="', ctx)).to.equal(true)
})
describe('complex expression', function () {
it('should support value or value', async function () {
expect(await evalExp('false or true', scope)).to.equal(true)
expect(await evalExp('false or true', ctx)).to.equal(true)
})
it('should support < and contains', async function () {
expect(await evalExp('1<2 and x contains "x"', scope)).to.equal(false)
expect(await evalExp('1<2 and x contains "x"', ctx)).to.equal(false)
})
it('should support < or contains', async function () {
expect(await evalExp('1<2 or x contains "x"', scope)).to.equal(true)
expect(await evalExp('1<2 or x contains "x"', ctx)).to.equal(true)
})
it('should support value and !=', async function () {
expect(await evalExp('empty and empty != ""', scope)).to.equal(false)
expect(await evalExp('empty and empty != ""', ctx)).to.equal(false)
})
})
it('should eval range expression', async function () {
expect(await evalExp('(2..4)', scope)).to.deep.equal([2, 3, 4])
expect(await evalExp('(two..4)', scope)).to.deep.equal([2, 3, 4])
expect(await evalExp('(2..4)', ctx)).to.deep.equal([2, 3, 4])
expect(await evalExp('(two..4)', ctx)).to.deep.equal([2, 3, 4])
})
})
})
+8 -8
View File
@@ -2,16 +2,16 @@ import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import { Filter } from '../../../../src/template/filter/filter'
import Scope from '../../../../src/scope/scope'
import Context from '../../../../src/context/context'
chai.use(sinonChai)
const expect = chai.expect
describe('filter', function () {
let scope: Scope
let ctx: Context
beforeEach(function () {
Filter.clear()
scope = new Scope()
ctx = new Context()
})
it('should create default filter if not registered', async function () {
const result = new Filter('foo', [], false)
@@ -19,28 +19,28 @@ describe('filter', function () {
})
it('should render input if filter not registered', async function () {
expect(await new Filter('undefined', [], false).render('foo', scope)).to.equal('foo')
expect(await new Filter('undefined', [], false).render('foo', ctx)).to.equal('foo')
})
it('should call filter impl with corrct arguments', async function () {
const spy = sinon.spy()
Filter.register('foo', spy)
await new Filter('foo', ['33'], false).render('foo', scope)
await new Filter('foo', ['33'], false).render('foo', ctx)
expect(spy).to.have.been.calledWith('foo', 33)
})
it('should render a simple filter', async function () {
Filter.register('upcase', x => x.toUpperCase())
expect(await new Filter('upcase', [], false).render('foo', scope)).to.equal('FOO')
expect(await new Filter('upcase', [], false).render('foo', ctx)).to.equal('FOO')
})
it('should render filters with argument', async function () {
Filter.register('add', (a, b) => a + b)
expect(await new Filter('add', ['2'], false).render(3, scope)).to.equal(5)
expect(await new Filter('add', ['2'], false).render(3, ctx)).to.equal(5)
})
it('should render filters with multiple arguments', async function () {
Filter.register('add', (a, b, c) => a + b + c)
expect(await new Filter('add', ['2', '"c"'], false).render(3, scope)).to.equal('5c')
expect(await new Filter('add', ['2', '"c"'], false).render(3, ctx)).to.equal('5c')
})
it('should not throw when filter name illegal', function () {
+5 -5
View File
@@ -1,5 +1,5 @@
import * as chai from 'chai'
import Scope from '../../../src/scope/scope'
import Context from '../../../src/context/context'
import Output from '../../../src/template/output'
import OutputToken from '../../../src/parser/output-token'
import { Filter } from '../../../src/template/filter/filter'
@@ -12,7 +12,7 @@ describe('Output', function () {
})
it('should stringify objects', async function () {
const scope = new Scope({
const scope = new Context({
foo: { obj: { arr: ['a', 2] } }
})
const output = new Output({ value: 'foo' } as OutputToken, false)
@@ -20,19 +20,19 @@ describe('Output', function () {
return expect(html).to.equal('[object Object]')
})
it('should skip function property', async function () {
const scope = new Scope({ obj: { foo: 'foo', bar: (x: any) => x } })
const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const html = await output.render(scope)
return expect(html).to.equal('[object Object]')
})
it('should respect to .toString()', async () => {
const scope = new Scope({ obj: { toString: () => 'FOO' } })
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const str = await output.render(scope)
return expect(str).to.equal('FOO')
})
it('should respect to .toString()', async () => {
const scope = new Scope({ obj: { toString: () => 'FOO' } })
const scope = new Context({ obj: { toString: () => 'FOO' } })
const output = new Output({ value: 'obj' } as OutputToken, false)
const str = await output.render(scope)
return expect(str).to.equal('FOO')
+12 -12
View File
@@ -1,6 +1,6 @@
import * as chai from 'chai'
import Tag from '../../../src/template/tag/tag'
import Scope from '../../../src/scope/scope'
import Context from '../../../src/context/context'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import Liquid from '../../../src/liquid'
@@ -11,9 +11,9 @@ const expect = chai.expect
const liquid = new Liquid()
describe('tag', function () {
let scope: Scope
let ctx: Context
before(function () {
scope = new Scope({
ctx = new Context({
foo: 'bar',
arr: [2, 1],
bar: {
@@ -51,7 +51,7 @@ describe('tag', function () {
value: 'foo',
name: 'foo'
} as TagToken
await new Tag(token, [], liquid).render(scope)
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.called
})
@@ -70,30 +70,30 @@ describe('tag', function () {
} as TagToken
})
it('should call tag.render with scope', async function () {
await new Tag(token, [], liquid).render(scope)
expect(spy).to.have.been.calledWithMatch(scope)
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.calledWithMatch(ctx)
})
it('should resolve identifier hash', async function () {
await new Tag(token, [], liquid).render(scope)
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.calledWithMatch({}, {
aa: 'bar'
})
})
it('should accept space between key/value', async function () {
await new Tag(token, [], liquid).render(scope)
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.calledWithMatch({}, {
bb: 2
})
})
it('should resolve number value hash', async function () {
await new Tag(token, [], liquid).render(scope)
expect(spy).to.have.been.calledWithMatch(scope, {
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.calledWithMatch(ctx, {
cc: 2.3
})
})
it('should resolve property access hash', async function () {
await new Tag(token, [], liquid).render(scope)
expect(spy).to.have.been.calledWithMatch(scope, {
await new Tag(token, [], liquid).render(ctx)
expect(spy).to.have.been.calledWithMatch(ctx, {
dd: 'uoo'
})
})
+2 -2
View File
@@ -1,7 +1,7 @@
import * as chai from 'chai'
import * as sinonChai from 'sinon-chai'
import * as sinon from 'sinon'
import Scope from '../../../src/scope/scope'
import Context from '../../../src/context/context'
import { Filter } from '../../../src/template/filter/filter'
import Value from '../../../src/template/value'
@@ -105,7 +105,7 @@ describe('Value', function () {
Filter.register('date', date)
Filter.register('time', time)
const tpl = new Value('foo.bar | date: "b" | time:2', false)
const scope = new Scope({
const scope = new Context({
foo: { bar: 'bar' }
})
await tpl.value(scope)