Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Pratchett.js/Test/datagraph.tests.coffee
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2359 lines (1737 sloc)
92.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
support = require './support.coffee' | |
util = require '../Source/utilities.coffee' | |
assert = require 'assert' | |
sinon = require 'sinon' | |
expect = require('sinon-expect').enhance require('expect.js'), sinon, 'was' | |
__ = sinon.match | |
describe "Paws' Data types:", -> | |
Paws = require "../Source/Paws.coffee" | |
{ Reactor, parse | |
, Thing, Label, Execution, Native | |
, ThingSet, Relation, Liability, Combination, Position, Mask, Operation | |
, ResponsibilityError } = Paws | |
{ Context, Sequence, Expression } = parse | |
describe 'Thing', -> # ---- ---- ---- ---- ---- Thing | |
# FIXME: A lot of these tests are more ... integration-ey than unit-ey; and to boot, a lot of | |
# *actual* unit tests are absent. I need to extract some of these complicated, | |
# purpose-related tests into an *integration* suite, and then re-build this suite from | |
# scratch to achieve ≥80% *unit* coverage. | |
# ### Thing: Core functionality ### | |
it 'exists', -> | |
expect(Thing).to.be.ok() | |
expect(Thing).to.be.a 'function' | |
it 'has a UUID', -> | |
uuid_regex = /[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/ | |
expect((new Thing).id).to.match uuid_regex | |
it 'compares by identity', -> | |
thing1 = new Label 'foo' | |
thing2 = new Label 'foo' | |
expect(Thing::compare.call thing1, thing1).to.be true | |
expect(Thing::compare.call thing1, thing2).to.not.be true | |
it 'can easily construct a Relation *to* itself', -> | |
a_parent = new Thing; a_child = new Thing; another_child = new Thing | |
result = a_child.owned_by a_parent | |
expect(result).to.be.a Relation | |
expect(result).to.be.owned() | |
expect(result.from).to.be a_parent | |
expect(result.to) .to.be a_child | |
result = another_child.contained_by a_parent | |
expect(result).to.be.a Relation | |
expect(result).not.to.be.owned() | |
expect(result.from).to.be a_parent | |
expect(result.to) .to.be another_child | |
describe '~ Metadata', -> | |
it 'is an ordered list of metadata', -> | |
thing = new Thing | |
expect(thing).to.have.property 'metadata' | |
expect(thing.metadata).to.be.an 'array' | |
it 'has an implicit slot for the noughty by default', -> | |
thing = new Thing | |
expect(thing.metadata).to.have.length 1 | |
expect(thing.metadata[0]).to.be undefined | |
it 'can be configured with no noughty-slot', -> | |
bare_thing = new Thing.with(noughtify: no)() | |
expect(bare_thing.metadata).to.have.length 0 | |
it 'stores Relations to other Things', -> | |
child1 = new Thing; child2 = new Thing | |
thing = new Thing child1, child2 | |
expect(thing).to.have.property 'metadata' | |
expect(thing.metadata).to.be.an 'array' | |
expect(thing.metadata[1]).to.be.a Relation | |
expect(thing.metadata[1].to).to.be child1 | |
expect(thing.metadata[2]).to.be.a Relation | |
expect(thing.metadata[2].to).to.be child2 | |
it 'tracks ownership', -> | |
a_parent = new Thing; a_child = new Thing; another_child = new Thing | |
a_parent = new Thing a_child.owned_by(a_parent), another_child | |
expect(a_parent.metadata[1]).to.be.owned() | |
expect(a_parent.metadata[2]).to.not.be.owned() | |
a_parent.disown_at 1 | |
expect(a_parent.metadata[1]).to.not.be.owned() | |
expect(a_child.owners).to.be.empty() | |
a_parent.own_at 2 | |
expect(a_parent.metadata[2]).to.be.owned() | |
expect(another_child.owners).to.not.be.empty() | |
expect(another_child.is_owned_by a_parent).to.be yes | |
describe '~ Backlinks', -> | |
it 'are created on children added as ‘owned’', -> | |
a_parent = new Thing; a_child = new Thing; another_child = new Thing; other = new Thing | |
expect(a_child).to.have.property 'owners' | |
expect(a_child.owners).to.be.an 'array' | |
expect(a_child.owners).to.have.length 0 | |
a_parent.push a_child.owned_by(a_parent), other, another_child.owned_by(a_parent) | |
expect(a_child).to.have.property 'owners' | |
expect(a_child.owners).to.be.an 'array' | |
expect(a_child.owners).to.have.length 1 | |
expect(a_child.owners[0]).to.be.a Relation | |
expect(a_child.owners).to.contain a_parent.metadata[1] | |
expect(another_child.owners).to.contain a_parent.metadata[3] | |
expect(other.owners).to.be.empty() | |
it 'are maintained by other metadata-modifying operations', -> | |
a_parent = new Thing; a_child = new Thing; another_child = new Thing | |
a_parent.unshift a_child.owned_by(a_parent) | |
rel = a_parent.metadata[1] | |
expect(a_child.owners).to.contain rel | |
a_parent.set 1, another_child.owned_by(a_parent) | |
expect(a_parent.metadata[1]).to.not.equal rel | |
expect(another_child.owners).to.contain a_parent.metadata[1] | |
it 'are discarded when the child is removed', -> | |
first = new Thing; second = new Thing; third = new Thing | |
a_parent = new Thing first.owned_by(a_parent), second.owned_by(a_parent), third.owned_by(a_parent) | |
expect(first.owners) .to.have.length 1 | |
expect(second.owners).to.have.length 1 | |
expect(third.owners) .to.have.length 1 | |
a_parent.set 2, undefined | |
expect(second.owners).to.have.length 0 | |
a_parent.shift() | |
expect(first.owners) .to.have.length 0 | |
a_parent.pop() | |
expect(third.owners) .to.have.length 0 | |
it 'are removed *and* added when directly setting elements by index', -> | |
a_parent = new Thing; a_child = new Thing; another_child = new Thing; other = new Thing | |
a_parent.push a_child.owned_by(a_parent), other | |
original_relation = a_parent.metadata[1] | |
expect(a_child.owners).to.contain original_relation | |
a_parent.set 1, another_child.owned_by(a_parent) | |
expect(a_parent.metadata[1]).to.not.be original_relation | |
expect(a_child.owners).to.be.empty() | |
expect(another_child.owners).to.contain a_parent.metadata[1] | |
describe '~ Responsibility', -> | |
it 'is expressed as a set of current ‘custodians’', -> | |
a Thing | |
expect(a.thing).to.have.property 'custodians' | |
# ### Thing: Metadata methods ### | |
describe '::set', -> | |
it 'changes a metadata value', -> | |
child = new Thing | |
a_thing = new Thing child | |
expect(a_thing.at(1)).to.be child | |
a_thing.set 1, new Thing | |
expect(a_thing.at(1)).to.not.be child | |
it 'accepts a Thing', -> | |
replacement = new Thing | |
a_thing = new Thing(new Thing) | |
a_thing.set 1, replacement | |
expect(a_thing.at 1).to.be replacement | |
it 'accepts a Relation', -> | |
replacement = new Thing | |
a_thing = new Thing(new Thing) | |
a_thing.set 1, (new Relation a_thing, replacement) | |
expect(a_thing.at 1).to.be replacement | |
it 'accepts nothingness to signal removal', -> | |
a_thing = new Thing(new Thing) | |
a_thing.set 1, undefined | |
expect(a_thing.at 1).to.be undefined | |
it 'does not directly use the passed Relation object', -> | |
replacement = new Thing | |
a_relation = new Relation a_thing, replacement | |
a_thing = new Thing(new Thing) | |
a_thing.set 1, a_relation | |
expect(a_thing.metadata[1]).to.not.be a_relation | |
it 'changes the noughty', -> | |
replacement = new Thing | |
a_thing = new Thing | |
a_thing.set 0, replacement | |
expect(a_thing.at 0).to.be replacement | |
it 'changes values at any index', -> | |
child1 = new Thing; child2 = new Thing; child3 = new Thing | |
replacement = new Thing | |
a_thing = new Thing child1, child2, child3 | |
expect(a_thing.at 2).to.be child2 | |
a_thing.set 2, replacement | |
expect(a_thing.at 2).to.be replacement | |
it 'changes values at an arbitrary index, higher than that of any existing elements', -> | |
child = new Thing | |
a_thing = new Thing | |
expect(a_thing.at 1337).to.be undefined | |
a_thing.set 1337, child | |
expect(a_thing.at 1337).to.be child | |
describe '(ownership)', -> | |
it 'creates backlinks on newly-owned nodes', -> | |
a Thing; another Thing | |
expect(another.thing.is_owned_by a.thing).to.be no | |
a.thing.set(1, another.thing.owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be yes | |
it 'removes backlinks on replaced nodes', -> | |
a Thing; another Thing | |
a.thing.set(1, another.thing.owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be yes | |
a.thing.set(1, (some Thing).owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be no | |
describe '(responsibility)', -> | |
it 'dedicates newly-added-as-owned nodes to an existing custodian of the parent', -> | |
a Thing; another Thing | |
an Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
expect(a.thing .belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
a.thing.set 1, another.thing.owned_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
it 'does not dedicate added-as-contained nodes to an existing custodian of the parent', -> | |
a Thing; another Thing | |
an Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
expect(a.thing .belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
a.thing.set 1, another.thing.contained_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
it 'dedicates a newly-added-as-owned nodes to *multiple* custodians of the parent', -> | |
a Thing; another Thing | |
an Execution; another Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
a.thing.dedicate(new Liability another.execution, a.thing) | |
expect(another.thing.belongs_to another.execution, 'read').to.be no | |
a.thing.set 1, another.thing.owned_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to another.execution, 'read').to.be yes | |
it 'emancipates replaced nodes from an existing custodian of the parent', -> | |
a Thing; another Thing; some Thing | |
an Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
a.thing.set 1, another.thing.owned_by(a.thing) | |
expect(a.thing .belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
expect(some.thing .belongs_to an.execution, 'read').to.be no | |
a.thing.set 1, some.thing.owned_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
describe '(errors)', -> | |
it 'checks availability, and throws an ResponsibilityError if there is a conflict', -> | |
a Thing; another Thing | |
an Execution; another Execution | |
a.thing .dedicate new Liability(an.execution, a.thing, 'write') | |
another.thing.dedicate new Liability(another.execution, another.thing, 'read') | |
expect(-> a.thing.set 1, another.thing.owned_by(a.thing)).to | |
.throwException ResponsibilityError | |
expect(a.thing.at 1).to.be undefined | |
expect(a.thing .belongs_to an.execution, 'write').to.be yes | |
expect(another.thing.belongs_to another.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
describe '::push', -> | |
it 'adds metadata values', -> | |
a Thing; another Thing | |
expect(a.thing.metadata).to.have.length 1 | |
a.thing.push another.thing | |
expect(a.thing.metadata).to.have.length 2 | |
it 'accepts Things', -> | |
a Thing; another Thing | |
a.thing.push another.thing | |
expect(a.thing.at 1).to.be another.thing | |
it 'accepts Relations', -> | |
a Thing; another Thing | |
a.thing.push(new Relation a.thing, another.thing) | |
expect(a.thing.at 1).to.be another.thing | |
it 'accepts multiple arguments', -> | |
a Thing; a Relation, a.thing, new Thing; another Thing | |
a.thing.push a.thing, a.relation, another.thing | |
expect(a.thing.at 1).to.be a.thing | |
expect(a.thing.at 3).to.be another.thing | |
it 'defaults pushed Things to not being owned', -> | |
a Thing; another Thing | |
a.thing.push another.thing | |
expect(a.thing.owns_at 1).to.be no | |
it 'respects the declared ownership of passed Relations', -> | |
a Thing; another Thing | |
a.thing.push a.thing.owned_by(another.thing) | |
expect(a.thing.owns_at 1).to.be yes | |
it 'does not directly use passed Relation objects', -> | |
a Thing; another Thing | |
a Relation, a.thing, another.thing | |
a.thing.push a.relation | |
expect(a.thing.at 1).to.be another.thing | |
expect(a.thing.metadata[1]).to.not.be a.relation | |
it 'can add a noughty', -> | |
a_thing = new Thing.with(noughtify: no)() | |
noughty = new Thing | |
expect(a_thing.metadata).to.have.length 0 | |
a_thing.push noughty | |
expect(a_thing.metadata).to.have.length 1 | |
describe '(ownership)', -> | |
it 'creates backlinks on newly-owned nodes', -> | |
a Thing; another Thing | |
expect(another.thing.is_owned_by a.thing).to.be no | |
a.thing.set(1, another.thing.owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be yes | |
it 'removes backlinks on replaced nodes', -> | |
a Thing; another Thing | |
a.thing.set(1, another.thing.owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be yes | |
a.thing.set(1, (some Thing).owned_by a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be no | |
describe '(ownership)', -> | |
it 'creates backlinks on any newly-owned nodes', -> | |
a Thing; some Thing; another Thing | |
expect(another.thing.is_owned_by a.thing).to.be no | |
a.thing.push some.thing, another.thing.owned_by(a.thing) | |
expect(another.thing.is_owned_by a.thing).to.be yes | |
expect(some.thing .is_owned_by a.thing).to.be no | |
describe '(responsibility)', -> | |
it 'dedicates newly-added-as-owned nodes to an existing custodian of the parent', -> | |
a Thing; another Thing | |
an Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
expect(a.thing .belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
a.thing.push another.thing.owned_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
it 'does not dedicate added-as-contained nodes to an existing custodian of the parent', -> | |
a Thing; some Thing; another Thing | |
an Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
expect(a.thing .belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
a.thing.push some.thing.owned_by(a.thing), another.thing | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
it 'dedicates a newly-added-as-owned nodes to *multiple* custodians of the parent', -> | |
a Thing; another Thing | |
an Execution; another Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
a.thing.dedicate(new Liability another.execution, a.thing) | |
expect(another.thing.belongs_to another.execution, 'read').to.be no | |
a.thing.push another.thing.owned_by(a.thing) | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to another.execution, 'read').to.be yes | |
# phew. | |
it 'dedicates *multiple* owned nodes to *multiple* custodians', -> | |
a Thing; some Thing; another Thing | |
an Execution; another Execution | |
a.thing.dedicate(new Liability an.execution, a.thing) | |
a.thing.dedicate(new Liability another.execution, a.thing) | |
expect(some.thing.belongs_to an.execution, 'read').to.be no | |
expect(some.thing.belongs_to another.execution, 'read').to.be no | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
expect(another.thing.belongs_to another.execution, 'read').to.be no | |
a.thing.push some.thing.owned_by(a.thing), another.thing.owned_by(a.thing) | |
expect(some.thing.belongs_to an.execution, 'read').to.be yes | |
expect(some.thing.belongs_to another.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be yes | |
expect(another.thing.belongs_to another.execution, 'read').to.be yes | |
describe '(errors)', -> | |
it 'checks availability, and throws an ResponsibilityError if there is a conflict', -> | |
a Thing; another Thing | |
an Execution; another Execution | |
a.thing .dedicate new Liability(an.execution, a.thing, 'write') | |
another.thing.dedicate new Liability(another.execution, another.thing, 'read') | |
expect(-> a.thing.push another.thing.owned_by(a.thing)).to | |
.throwException ResponsibilityError | |
expect(a.thing.at 1).to.be undefined | |
expect(a.thing .belongs_to an.execution, 'write').to.be yes | |
expect(another.thing.belongs_to another.execution, 'read').to.be yes | |
expect(another.thing.belongs_to an.execution, 'read').to.be no | |
# XXX: This is not thoroughly tested, due to the amount of duplication with ::push. | |
describe '::unshift', -> | |
it 'adds metadata values', -> | |
a Thing; another Thing | |
expect(a.thing.metadata).to.have.length 1 | |
a.thing.unshift another.thing | |
expect(a.thing.metadata).to.have.length 2 | |
it 'places added values after the noughty', -> | |
a Thing; another Thing | |
a.thing.unshift another.thing | |
expect(a.thing.at 0).to.not.be another.thing | |
expect(a.thing.at 1).to.be another.thing | |
it 'places added values before existing elements', -> | |
a Thing; some Thing; another Thing | |
a.thing.unshift some.thing | |
expect(a.thing.at 1).to.be some.thing | |
a.thing.unshift another.thing | |
expect(a.thing.at 1).to.be another.thing | |
expect(a.thing.at 2).to.be some.thing | |
it 'accepts multiple arguments', -> | |
a Thing; a Relation, a.thing, new Thing; another Thing | |
a.thing.unshift a.thing, a.relation, another.thing | |
expect(a.thing.at 1).to.be a.thing | |
expect(a.thing.at 3).to.be another.thing | |
it 'defaults prepended Things to not being owned', -> | |
a Thing; another Thing | |
a.thing.unshift another.thing | |
expect(a.thing.owns_at 1).to.be no | |
it 'respects the declared ownership of passed Relations', -> | |
a Thing; another Thing | |
a.thing.unshift a.thing.owned_by(another.thing) | |
expect(a.thing.owns_at 1).to.be yes | |
it 'does not directly use passed Relation objects', -> | |
a Thing; another Thing | |
a Relation, a.thing, another.thing | |
a.thing.unshift a.relation | |
expect(a.thing.at 1).to.be another.thing | |
expect(a.thing.metadata[1]).to.not.be a.relation | |
it 'can add a noughty', -> | |
a_thing = new Thing.with(noughtify: no)() | |
noughty = new Thing | |
expect(a_thing.metadata).to.have.length 0 | |
a_thing.unshift noughty | |
expect(a_thing.metadata).to.have.length 1 | |
describe '::clone', -> | |
it 'creates a new Thing', -> | |
thing = new Thing | |
expect(thing.clone()).to.not.be thing | |
it 'duplicates the metadata of the receiver', -> | |
thing = new Thing new Thing, new Thing, new Thing | |
clone = thing.clone() | |
expect(clone.metadata).to.have.length 4 | |
clone.metadata.forEach (rel, i) -> if rel | |
expect(clone.at i).to.be.ok() | |
expect(rel).not.to.be thing.metadata[i] | |
expect(rel.to).to.be thing.metadata[i].to | |
it 'updates the `from`-linkage on the copied metadata', -> | |
thing = new Thing new Thing, new Thing, new Thing | |
clone = thing.clone() | |
clone.metadata.forEach (rel, i) -> if rel | |
expect(rel.from).to.not.be thing | |
expect(rel.from).to.be clone | |
it 'handles empty elements gracefully', -> | |
thing = new Thing new Thing, undefined, new Thing | |
expect(-> thing.clone()).to.not.throwError() | |
it 'can copy metadata to an existing other-Thing instead of creating one', -> | |
thing1 = new Thing new Thing, new Thing, new Thing | |
thing2 = new Thing new Thing | |
old_metadata = thing2.metadata | |
result = thing1.clone(thing2) | |
expect(result).to.be thing2 | |
expect(thing2.metadata).to.not.be old_metadata | |
it 'does not copy active responsibility', -> | |
thing = new Thing new Thing, new Thing, new Thing | |
thing.dedicate a Liability, (an Execution), thing | |
expect(thing.custodians.direct).to.not.be.empty() | |
clone = thing.clone() | |
expect(clone.custodians.direct).to.be.empty() | |
describe '::toArray', -> | |
it 'reduces the receiver Thing to an Array', -> | |
it = new Thing | |
expect(-> it.toArray()).to.not.throwException() | |
expect( it.toArray()).to.be.an 'array' | |
expect( it.toArray()).to.be.empty() | |
another = new Thing new Thing, new Thing, new Thing | |
expect(another.toArray()).to.be.an 'array' | |
expect(another.toArray()).to.not.be.empty() | |
it 'returns Things, not the Relation objects from the receiver', -> | |
first = new Thing; second = new Thing; third = new Thing | |
it = new Thing first, second, third | |
array = it.toArray() | |
expect(array).to.have.length 3 | |
expect(array[0]).to.not.be.a Relation | |
expect(array[0]).to.be.a Thing | |
it 'excludes the noughty by default', -> | |
first = new Thing; second = new Thing; third = new Thing | |
it = new Thing first, second, third | |
array = it.toArray() | |
expect(array).to.have.length 3 | |
expect(array[0]).to.be first | |
it 'retains empty slots', -> | |
first = new Thing; third = new Thing | |
it = new Thing first, undefined, third | |
array = it.toArray() | |
expect(array).to.have.length 3 | |
expect(array[0]).to.be first | |
expect(array[1]).to.be undefined | |
expect(array[2]).to.be third | |
describe 'pair()', -> | |
it 'creates a new Thing', -> | |
expect(Thing.pair()).to.be.a Thing | |
it 'turns the first argument into a Label', -> | |
a_pair = Thing.pair 'foo' | |
expect(a_pair.at 1).to.be.a Label | |
expect(a_pair.at(1).alien).to.be 'foo' | |
a_pair = Thing.pair Label('bar') | |
expect(a_pair.at 1).to.be.a Label | |
expect(a_pair.at(1).alien).to.be 'bar' | |
it 'creates the pair as owning the key-Label', -> | |
foo = new Thing | |
a_pair = Thing.pair 'foo', foo | |
expect(a_pair.at(1).alien).to.be 'foo' | |
expect(a_pair.metadata[1]).to.be.owned() | |
it 'creates the pair as *not* owning the value', -> | |
foo = new Thing | |
a_pair = Thing.pair 'foo', foo | |
expect(a_pair.at(2)).to.be foo | |
expect(a_pair.metadata[2]).to.not.be.owned() | |
it 'can be instructed to create the pair as owning the value', -> | |
foo = new Thing | |
a_pair = Thing.pair 'foo', foo, yes | |
expect(a_pair.at(2)).to.be foo | |
expect(a_pair.metadata[2]).to.be.owned() | |
it 'takes existing ownership of a passed Relation by default', -> | |
foo = new Thing | |
rel = new Relation null, foo, yes | |
a_pair = Thing.pair 'foo', rel | |
expect(a_pair.at(2)).to.be foo | |
expect(a_pair.metadata[2]).to.be.owned() | |
it 'can be instructed to *override* existing ownership of a passed Relation', -> | |
foo = new Thing | |
rel = new Relation null, foo, yes | |
a_pair = Thing.pair 'foo', rel, no | |
expect(a_pair.at(2)).to.be foo | |
expect(a_pair.metadata[2]).to.not.be.owned() | |
describe '::define', -> | |
it 'adds a pair to the end of the receiver', -> | |
a_thing = Thing.construct foo: new Thing | |
another_thing = new Thing | |
a_thing.define 'bar', another_thing | |
expect(a_thing.find('bar')[0].isPair()).to.be yes | |
expect(a_thing.find('bar')[0].valueish()).to.be another_thing | |
it 'marks the pushed pair as owned by the receiver', -> | |
a_thing = Thing.construct foo: new Thing | |
another_thing = new Thing | |
a_thing.define 'bar', another_thing | |
expect(a_thing.owns_at 1).to.be yes | |
it 'defaults, however, to *not* owning the passed value', -> | |
a_thing = new Thing | |
another_thing = new Thing | |
a_thing.define 'bar', another_thing | |
pair = a_thing.at 1 | |
expect(pair.owns_at 2).to.be no | |
it 'accepts a Thing', -> | |
a_thing = Thing.construct foo: new Thing | |
another_thing = new Thing | |
a_thing.define 'bar', another_thing | |
expect(a_thing.find('bar')[0].isPair()).to.be yes | |
expect(a_thing.find('bar')[0].valueish()).to.be another_thing | |
it 'accepts a Relation', -> | |
a_thing = Thing.construct foo: new Thing | |
another_thing = new Thing | |
a_thing.define 'bar', (new Relation a_thing, another_thing) | |
expect(a_thing.find('bar')[0].isPair()).to.be yes | |
expect(a_thing.find('bar')[0].valueish()).to.be another_thing | |
it 'checks availability, and throws an ResponsibilityError if there is a conflict', -> | |
a_thing = new Thing; another_thing = new Thing | |
an_execution = new Execution; another_execution = new Execution | |
a_thing .dedicate new Liability(an_execution, a_thing, 'write') | |
another_thing.dedicate new Liability(another_execution, another_thing, 'read') | |
expect(-> a_thing.define 'foo', another_thing.owned_by(a_thing)).to | |
.throwException ResponsibilityError | |
expect(a_thing.at 1).to.be undefined | |
expect(a_thing .belongs_to an_execution, 'write').to.be yes | |
expect(another_thing.belongs_to another_execution, 'read').to.be yes | |
expect(another_thing.belongs_to an_execution, 'read').to.be no | |
describe '::find', -> | |
first = new Thing; second = new Thing; third = new Thing | |
foo_bar_foo = new Thing Thing.pair('foo', first), | |
Thing.pair('bar', second), | |
Thing.pair('foo', third) | |
it 'produces an Array of Things', -> | |
expect(foo_bar_foo.find Label 'nope').to.be.an 'array' | |
it 'finds Things matching a given key', -> | |
results = foo_bar_foo.find Label 'foo' | |
expect(results.length).to.be.greaterThan 0 | |
results.forEach (result) -> | |
expect(result).to.be.a Thing | |
it "excludes Things that don't match the key", -> | |
results = foo_bar_foo.find Label 'foo' | |
expect(results).to.have.length 2 | |
expect(util.pluck results, 'metadata.2.to').to.not.contain second # FIXME: ugly. | |
it 'produces the results in reverse order', -> | |
results = foo_bar_foo.find Label 'foo' | |
expect(results[0].valueish()).to.be third | |
expect(results[1].valueish()).to.be first | |
it 'handles Things with non-pair members gracefully', -> | |
thing = new Thing Thing.pair('foo', first), | |
new Thing, | |
Thing.pair('bar', second), | |
Thing.pair('foo', third) | |
expect(thing.find Label 'foo').to.have.length 2 | |
it 'can take a JavaScript primitive as a key instead of a Label', -> | |
expect(foo_bar_foo.find 'bar').to.have.length 1 | |
describe '~ The root `receiver`', -> | |
caller = undefined; receiver = undefined | |
beforeEach -> | |
caller = new Execution | |
receiver = Thing::receiver.clone() | |
it 'preforms a `::find`', -> | |
a_thing = Thing.construct foo: another_thing = new Thing | |
params = Execution.create_params caller, a_thing, new Label 'foo' | |
sinon.spy a_thing, 'find' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(a_thing.find).was.calledOnce() | |
# FIXME: This should really be an *integration* test. | |
it 'finds a matching pair-ish Thing in the subject', -> | |
a_thing = Thing.construct foo: another_thing = new Thing | |
params = Execution.create_params caller, a_thing, new Label 'foo' | |
sinon.spy caller, 'queue' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(caller.queue).was.calledWith __.has 'params', [another_thing] | |
it 'stages the caller if there is a result', -> | |
a_thing = Thing.construct foo: another_thing = new Thing | |
params = Execution.create_params caller, a_thing, new Label 'foo' | |
sinon.spy caller, 'queue' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(caller.queue).was.calledOnce() | |
it 'does not stage the caller if there is no result', -> | |
a_thing = Thing.construct foo: another_thing = new Thing | |
params = Execution.create_params caller, a_thing, new Label 'bar' | |
sinon.spy caller, 'queue' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(caller.queue).was.notCalled() | |
# NB: A lot of this is duplicating effort from the Giraphe tests; but hey. | |
describe '::_walk_descendants', -> | |
it 'exists', -> | |
a Thing | |
expect(a.thing._walk_descendants).to.be.a 'function' | |
it "doesn't throw when given no arguments", -> | |
expect(-> (a Thing)._walk_descendants()).to.not.throwException() | |
it 'accepts a callback', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
expect(-> a.thing._walk_descendants(->)).to.not.throwException() | |
it.skip 'provides a method to cache results during a particular reactor-step' | |
it 'returns a mapping object', -> | |
a Thing | |
rv = a.thing._walk_descendants() | |
expect(rv).to.be.an 'object' | |
it 'collects owned descendants into the returned object', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
rv = a.thing._walk_descendants() | |
expect(util.values(rv)).to.contain foo | |
expect(rv[foo.id]).to.be foo | |
it 'calls the callback on each node walked', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
a.thing._walk_descendants cb = sinon.spy() | |
expect(cb).was.calledOn a.thing | |
expect(cb).was.calledOn foo | |
expect(cb).was.calledOn bar | |
expect(cb).was.calledOn widget | |
it 'skips objects for which the callback returns false', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
descendants = a.thing._walk_descendants cb = sinon.spy -> | |
return false if this is bar | |
expect(cb).was.calledOn bar | |
expect(descendants[foo.id]).to.be foo | |
expect(descendants[bar.id]).to.be undefined | |
it 'can be instructed to cease iteration', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
rv = a.thing._walk_descendants cb = sinon.spy -> | |
return Thing.abortIteration if this is foo | |
expect(rv).to.not.be.ok() | |
it 'skips descendants of objects for which the callback returns false', -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = new Thing | |
descendants = a.thing._walk_descendants cb = sinon.spy -> | |
return false if this is bar | |
# FIXME: Yes, this is awkwardly-worded. It's pending sinon-expect#5: | |
# <https://github.com/lightsofapollo/sinon-expect/issues/5> | |
expect(!cb.calledOn widget) | |
expect(descendants[widget.id]).to.be undefined | |
it "doesn't touch contained (not-owned) descendants", -> | |
a.thing = Thing.construct | |
foo: foo = new Thing, bar: bar = Thing.construct | |
widget: widget = (new Thing).contained_by(bar) | |
descendants = a.thing._walk_descendants cb = sinon.spy() | |
# FIXME: See above. | |
expect(!cb.calledOn widget) | |
expect(descendants[widget.id]).to.be undefined | |
it 'touches each descendant only once, in the presence of cyclic graphcs', -> | |
a.thing = Thing.construct | |
child: child = new Thing | |
child.define('cyclic', a.thing) | |
a.thing._walk_descendants cb = sinon.spy() | |
# FIXME: This could probably be better expressed (slash less-tightly-coupled) | |
expect(cb.callCount).to.be 6 | |
# ### Thing: Responsibility methods ### | |
describe '::available_to', -> | |
it 'exists', -> | |
expect((a Thing).available_to).to.be.a 'function' | |
it 'accepts a Liability', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
expect(-> a_thing.available_to a.liability).to.not.throwException() | |
it 'accepts multiple Liabilities', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
another Liability, (another Execution), a_thing | |
expect(-> a_thing.available_to a.liability, another.liability).to.not.throwException() | |
it 'accepts an Array of Liabilities', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
another Liability, (another Execution), a_thing | |
expect(-> a_thing.available_to [a.liability, another.liability]).to.not.throwException() | |
it 'succeeds if the root has no children and is not adopted', -> | |
expect((a Thing).available_to a Liability).to.be yes | |
it "succeeds if none of the receiver's descendants are adopted", -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
expect(a_thing.available_to a Liability).to.be true | |
it 'succeeds if the receiver is already adopted by the Execution', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
a_thing.dedicate new Liability an.execution, a_thing | |
expect(a_thing.available_to a.liability).to.be yes | |
it 'succeeds if the receiver is already adopted by all of the Executions', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
another Liability, (another Execution), a_thing | |
a_thing.dedicate new Liability an.execution, a_thing | |
a_thing.dedicate new Liability another.execution, a_thing | |
expect(a_thing.available_to a.liability, another.liability).to.be yes | |
it 'fails if the receiver is adopted by someone else, and conflicts', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
a_thing.dedicate (some Liability, (another Execution), a_thing, 'write') | |
expect(a_thing.available_to a.liability).to.be no | |
it 'fails when an owned descendant is adopted by someone else, and conflicts', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
widget.dedicate (some Liability, (another Execution), widget, 'write') | |
expect(a_thing.available_to a.liability).to.be no | |
it 'fails when only one argument conflicts with such an adopted owned-descendant', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing, 'read' | |
another Liability, (another Execution), a_thing, 'write' | |
widget.dedicate (some Liability, (another Execution), widget, 'read') | |
expect(a_thing.available_to a.liability, another.liability).to.be no | |
describe '::belongs_to', -> | |
it 'exists', -> | |
expect((a Thing).belongs_to).to.be.a 'function' | |
it 'accepts an Execution', -> | |
expect(-> (a Thing).belongs_to an Execution).to.not.throwException() | |
it 'accepts a Liability', -> | |
expect(-> (a Thing).belongs_to a Liability).to.not.throwException() | |
it 'accepts a Thing with parents', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
expect(-> widget.belongs_to a.Liability).to.not.throwException() | |
it 'indicates false if there are no custodians', -> | |
expect((a Thing).belongs_to a Liability).to.be no | |
describe '~ Direct responsibility', -> | |
it 'succeeds if the receiver belongs to the passed Liability', -> | |
(a Thing).dedicate a Liability, new Execution, a.thing | |
expect(a.thing.belongs_to a.liability).to.be yes | |
it 'fails if it has other custodians, but not the passed Liability', -> | |
(a Thing).dedicate a Liability, (an Execution), a.thing, 'write' | |
another Liability, (another Execution), a.thing, 'read' | |
expect(a.thing.belongs_to another.liability).to.be no | |
it 'succeeds if the receiver already belongs to the Exec with the same license', -> | |
(a Thing).dedicate a Liability, (an Execution), a.thing, 'read' | |
expect(a.thing.belongs_to an.execution, 'read').to.be yes | |
it 'succeeds if it already belongs to the the Exec with a greater license', -> | |
(a Thing).dedicate a Liability, (an Execution), a.thing, 'write' | |
expect(a.thing.belongs_to an.execution, 'read').to.be yes | |
it 'fails if it already belongs to the the Exec with a lesser license', -> | |
(a Thing).dedicate a Liability, (an Execution), a.thing, 'read' | |
expect(a.thing.belongs_to an.execution, 'write').to.be no | |
it 'fails if it has other custodians, but not the passed Exec', -> | |
(a Thing).dedicate a Liability, (an Execution), a.thing, 'write' | |
expect(a.thing.belongs_to (another Execution), 'read').to.be no | |
describe '~ Indirect responsibility', -> | |
it 'succeeds if a parent of the receiver belongs to the passed Liability', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing | |
expect(widget.belongs_to a.liability).to.be yes | |
it 'fails if a parent has other custodians, but not the passed Liability', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing, 'write' | |
another Liability, (another Execution), a_thing, 'read' | |
expect(widget.belongs_to another.liability).to.be no | |
it 'succeeds if a parent already belongs to the Exec with the same license', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing, 'read' | |
expect(widget.belongs_to an.execution, 'read').to.be yes | |
it 'succeeds if a parent already belongs to the the Exec with a greater license', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing, 'write' | |
expect(widget.belongs_to an.execution, 'read').to.be yes | |
it 'fails if a parent already belongs to the the Exec with a lesser license', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing, 'read' | |
expect(widget.belongs_to an.execution, 'write').to.be no | |
it 'fails if a parent has other custodians, but not the passed Exec', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a_thing.dedicate a Liability, (an Execution), a_thing, 'write' | |
expect(widget.belongs_to (another Execution), 'read').to.be no | |
describe '::dedicate', -> | |
it 'exists', -> | |
expect((a Thing).dedicate).to.be.a 'function' | |
it 'adds a passed Liability to the custodians', -> | |
a Liability, (an Execution), a Thing | |
expect(a.thing.custodians.direct).to.be.empty() | |
rv = a.thing.dedicate a.liability | |
expect(rv).to.be yes | |
expect(a.thing.custodians.direct).to.be.an 'array' | |
expect(a.thing.custodians.direct).to.contain a.liability | |
it 'climbs descendants, adding the Liability to every owned node', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
rv = a_thing.dedicate a.liability | |
expect(rv).to.be yes | |
expect(foo .custodians.inherited).to.contain a.liability | |
expect(widget.custodians.inherited).to.contain a.liability | |
it 'succeeds if there is existing, *non-conflicting* responsibility on the receiver', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing, 'read' | |
another Liability, (another Execution), a_thing, 'read' | |
a_thing.dedicate a.liability | |
rv = a_thing.dedicate another.liability | |
expect(rv).to.be yes | |
expect(a_thing.custodians.direct ).to.contain a.liability | |
expect(a_thing.custodians.direct ).to.contain another.liability | |
expect(widget .custodians.inherited).to.contain a.liability | |
expect(widget .custodians.inherited).to.contain another.liability | |
it 'fails if there is conflicting responsibility on the receiver', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing, 'write' | |
another Liability, (another Execution), a_thing, 'read' | |
a_thing.dedicate a.liability | |
rv = a_thing.dedicate another.liability | |
expect(rv).to.be no | |
expect(a_thing.custodians.direct ).to.not.contain another.liability | |
expect(widget .custodians.inherited).to.not.contain another.liability | |
it 'fails if there is conflicting responsibility a descendant', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), widget, 'write' | |
another Liability, (another Execution), a_thing, 'read' | |
widget.dedicate a.liability | |
rv = a_thing.dedicate another.liability | |
expect(rv).to.be no | |
expect(a_thing.custodians.direct).to.not.contain another.liability | |
expect(a_thing.custodians.direct).to.be.empty() | |
it "adds multiple passed Liabilities to the receiver's custodians", -> | |
a Liability, (an Execution), a Thing | |
another Liability, (another Execution), a.thing | |
rv = a.thing.dedicate a.liability, another.liability | |
expect(rv).to.be yes | |
expect(a.thing.custodians.direct).to.contain a.liability | |
expect(a.thing.custodians.direct).to.contain another.liability | |
it 'climbs descendants, adding all Liabilities to every owned node', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
another Liability, (another Execution), a_thing | |
rv = a_thing.dedicate a.liability, another.liability | |
expect(rv).to.be yes | |
expect(foo .custodians.inherited).to.contain a.liability | |
expect(foo .custodians.inherited).to.contain another.liability | |
expect(widget.custodians.inherited).to.contain a.liability | |
expect(widget.custodians.inherited).to.contain another.liability | |
it 'adds *no* liabilities if there is conflicting responsibility on the receiver', -> | |
# NOTE: It's important that this addition fails on the *second* liability added; it's | |
# explicitly supposed to be testing that the first, *valid* liability isn't | |
# accidetnally left hanging around. | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
some Liability, (some Execution), a_thing, 'read' | |
a Liability, (an Execution), a_thing, 'read' | |
another Liability, (another Execution), a_thing, 'write' | |
a_thing.dedicate some.liability | |
rv = a_thing.dedicate a.liability, another.liability | |
expect(rv).to.be no | |
expect(a_thing.custodians.direct ).to.not.contain a.liability | |
expect(a_thing.custodians.direct ).to.not.contain another.liability | |
expect(widget .custodians.inherited).to.not.contain a.liability | |
expect(widget .custodians.inherited).to.not.contain another.liability | |
it 'adds *no* liabilities if there is conflicting responsibility on a descendant', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
some Liability, (some Execution), widget, 'read' | |
a Liability, (an Execution), a_thing, 'read' | |
another Liability, (another Execution), a_thing, 'write' | |
widget.dedicate some.liability | |
rv = a_thing.dedicate a.liability, another.liability | |
expect(rv).to.be no | |
expect(a_thing.custodians.direct ).to.not.contain a.liability | |
expect(a_thing.custodians.direct ).to.not.contain another.liability | |
expect(widget .custodians.inherited).to.not.contain a.liability | |
expect(widget .custodians.inherited).to.not.contain another.liability | |
describe '::emancipate', -> | |
it 'exists', -> | |
expect((a Thing).emancipate).to.be.a 'function' | |
it "succeeds if the receiver didn't belong to the Liability", -> | |
a Liability, (an Execution), a Thing | |
rv = a.thing.emancipate a.liability | |
expect(rv).to.be yes | |
it 'succeeds if the receiver belongs to the Liability', -> | |
a Liability, (an Execution), a Thing | |
a.thing.dedicate a.liability | |
rv = a.thing.emancipate a.liability | |
expect(rv).to.be yes | |
it 'removes the passed Liability from the custodians of this node', -> | |
a Liability, (an Execution), a Thing | |
a.thing.dedicate a.liability | |
expect(a.thing.custodians.direct).to.contain a.liability | |
rv = a.thing.emancipate a.liability | |
expect(rv).to.be yes | |
expect(a.thing.custodians.direct).to.not.contain a.liability | |
it 'climbs descendants, removing the Liability from every owned node', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
a_thing.dedicate a.liability | |
expect(foo .custodians.inherited).to.contain a.liability | |
expect(widget.custodians.inherited).to.contain a.liability | |
rv = a_thing.emancipate a.liability | |
expect(rv).to.be yes | |
expect(foo .custodians.inherited).not.to.contain a.liability | |
expect(widget.custodians.inherited).not.to.contain a.liability | |
it 'succeeds if the receiver belongs to at least one of the Liabilities', -> | |
a Liability, (an Execution), a Thing | |
another Liability, (another Execution), a.thing | |
a.thing.dedicate another.liability | |
rv = a.thing.emancipate a.liability, another.liability | |
expect(rv).to.be yes | |
it 'removes all passed Liabilities from the custodians of this node', -> | |
a Liability, (an Execution), a Thing | |
another Liability, (another Execution), a.thing | |
a.thing.dedicate another.liability | |
rv = a.thing.emancipate a.liability, another.liability | |
expect(rv).to.be yes | |
expect(a.thing.custodians.direct).to.not.contain a.liability | |
it 'climbs descendants, removing all Liabilities from every owned node', -> | |
a_thing = Thing.construct foo: foo = new Thing, bar: | |
bar = Thing.construct widget: widget = new Thing | |
a Liability, (an Execution), a_thing | |
another Liability, (another Execution), a_thing | |
a_thing.dedicate another.liability | |
rv = a_thing.emancipate a.liability, another.liability | |
expect(rv).to.be yes | |
expect(foo .custodians.inherited).not.to.contain another.liability | |
expect(widget.custodians.inherited).not.to.contain another.liability | |
# ### Thing: Utility / convenience methods and functions ### | |
describe '.construct', -> | |
it 'constructs a new Thing', -> | |
expect(Thing.construct()).to.be.a Thing | |
it 'constructs Things with a noughty-slot', -> | |
constructee = Thing.construct() | |
expect(constructee.metadata).to.have.length 1 | |
expect(constructee.at 0).to.be undefined | |
it 'constructs pairs for the new Thing', -> | |
a_thing = new Thing | |
constructee = Thing.construct {foo: a_thing} | |
expect(constructee.metadata).to.have.length 2 | |
pair = constructee.at 1 | |
expect(pair).to.be.a Thing | |
expect(pair.isPair()).to.be true | |
it "turns passed JavaScript objects' keys into corresponding definition-pairs", -> | |
a_thing = new Thing | |
constructee = Thing.construct {foo: a_thing} | |
expect(constructee.metadata).to.have.length 2 | |
pair = constructee.at 1 | |
expect(pair.at(1).alien).to.be 'foo' | |
expect(pair.at 2).to.be a_thing | |
it 'successfully constructs multiple such definition-pairs', -> | |
thing_1 = new Thing; thing_2 = new Thing | |
constructee = Thing.construct {foo: thing_1, bar: thing_2} | |
expect(constructee.metadata).to.have.length 3 | |
expect(constructee.find('foo')[0].valueish()).to.be thing_1 | |
expect(constructee.find('bar')[0].valueish()).to.be thing_2 | |
it 'creates the construct as owning its definition-pairs', -> | |
constructee = Thing.construct {something: new Thing} | |
expect(constructee.metadata[1]).to.be.owned() | |
it 'also creates the construct as owning its *members*, by default', -> | |
constructee = Thing.construct {something: new Thing} | |
expect(constructee.at(1).metadata[2]).to.be.owned() | |
it "can be instructed to create structures that *don't* own their members", -> | |
constructee = Thing.with(own: no).construct {something: new Thing} | |
expect(constructee.metadata[1]).to.be.owned() | |
expect(constructee.at(1).metadata[2]).to.not.be.owned() | |
it 'generates nested structures', -> | |
constructee = Thing.construct { foo: { bar: {baz: something = new Thing} } } | |
first_pair = constructee.at 1 | |
expect(first_pair ).to.be.a Thing | |
expect(first_pair.keyish().alien).to.be 'foo' | |
expect(first_pair.valueish() ).to.be.a Thing | |
second_pair = first_pair.valueish().at 1 | |
expect(second_pair ).to.be.a Thing | |
expect(second_pair.keyish().alien).to.be 'bar' | |
expect(second_pair.valueish() ).to.be.a Thing | |
third_pair = second_pair.valueish().at 1 | |
expect(third_pair ).to.be.a Thing | |
expect(third_pair.keyish().alien).to.be 'baz' | |
expect(third_pair.valueish() ).to.be something | |
it 'passes Functions onwards to Native.synchronous', -> | |
constructee = Thing.construct {foo: new Function} | |
pair = constructee.at 1 | |
expect(pair.at 1).to.be.a Label | |
expect(pair.at 2).to.be.an Execution | |
expect(pair.at 2).to.have.property 'synchronous' | |
it 'names the passed objects, if requested', -> | |
a Thing | |
expect(a.thing.name).to.be undefined | |
Thing.with(names: yes).construct {something: a.thing} | |
expect(a.thing.name).to.be 'something' | |
# ### Thing: Supporting types ### | |
describe 'ThingSet', -> # ---- ---- ---- ---- ---- ThingSet | |
it 'exists', -> | |
expect(ThingSet).to.be.ok() | |
expect(ThingSet).to.be.a 'function' | |
it 'starts out with no length', -> | |
a ThingSet | |
expect(a.thingset.size()).to.be 0 | |
it 'gets larger when Things are added', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.size()).to.be 1 | |
it 'tracks when a given Thing has been added', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.has(a.thing)).to.be true | |
it "doesn't confuse one Thing for another", -> | |
a Thing; another Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.has(another.thing)).to.be false | |
it 'does not get larger when an already-present Thing is added', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.size()).to.be 1 | |
a.thingset.add(a.thing) | |
expect(a.thingset.size()).to.be 1 | |
it 'can remove Things', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.has(a.thing)).to.be true | |
a.thingset.delete(a.thing) | |
expect(a.thingset.has(a.thing)).to.be false | |
it 'reports whether a Thing was actually removed when deleting it', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.delete(a.thing)).to.be true | |
expect(a.thingset.delete(a.thing)).to.be false | |
it 'shrinks when Things are deleted', -> | |
a Thing; a ThingSet | |
a.thingset.add(a.thing) | |
expect(a.thingset.size()).to.be 1 | |
a.thingset.delete(a.thing) | |
expect(a.thingset.size()).to.be 0 | |
it 'can contain multiple different Things', -> | |
a Thing; another Thing; a ThingSet | |
a.thingset.add(a.thing) | |
a.thingset.add(another.thing) | |
expect(a.thingset.size()).to.be 2 | |
a.thingset.delete(a.thing) | |
expect(a.thingset.size()).to.be 1 | |
expect(a.thingset.has(a.thing)).to.be false | |
expect(a.thingset.has(another.thing)).to.be true | |
describe 'Relation', -> # ---- ---- ---- ---- ---- Relation | |
it 'exists', -> | |
expect(Relation).to.be.ok() | |
expect(Relation).to.be.a 'function' | |
it 'expresses a containing / non-owning relationship, by default', -> | |
expect((new Relation).owns).to.be no | |
it 'can create an owning relationship, as well', -> | |
rel = new Relation undefined, undefined, yes | |
expect(rel.owns).to.be yes | |
it 'if passed an existing relation during construction, copies itself from that', -> | |
existing = new Relation new Thing, new Thing, yes | |
clone = new Relation existing | |
expect(clone.from).to.not.be.a Relation # /reg | |
expect(new Relation existing).to.not.be existing | |
expect(new Relation existing).to.eql existing | |
new_parent = new Thing | |
another_clone = new Relation new_parent, existing | |
expect(another_clone).to.not.be existing | |
expect(another_clone.owns).to.equal existing.owns | |
expect(another_clone.to) .to.equal existing.to | |
expect(another_clone.from).to.not.equal existing.from | |
expect(another_clone.from).to.be new_parent | |
describe '::clone', -> | |
it 'creates a new Relation', -> | |
a_thing = new Thing; another_thing = new Thing | |
rel = new Relation a_thing, another_thing | |
expect(rel.clone() ).to.not.be rel | |
expect(rel.clone().from).to.be a_thing | |
expect(rel.clone().to ).to.be another_thing | |
expect(rel.clone().owns).to.be no | |
it "doesn't change the existing ownership of a cloned Relation", -> | |
rel = new Relation new Thing, new Thing, yes | |
expect(rel.clone().owns).to.be yes | |
it.skip 'does not copy data to a provided `other`, as Relations are immutable! /reg', -> | |
# I'm going to need a more robust ‘nuhhuh’ system for mutating Relations. As noted in | |
# `datagraph.coffee`, I'm thinking about making Relations immutable *once they've been | |
# included in a Thing*. | |
rel = new Relation Thing(), Thing(); other = new Relation Thing(), Thing() | |
expect(rel.clone other).to.not.be other | |
expect(rel.clone other).to.not.eql other | |
describe 'Liability', -> # ---- ---- ---- ---- ---- Liability | |
it 'exists', -> | |
expect(Liability).to.be.ok() | |
expect(Liability).to.be.a 'function' | |
it 'constructs successfully', -> | |
expect(-> new Liability).not.to.throwError() | |
expect(new Liability).to.be.a Liability | |
expect(-> Liability()).not.to.throwError() | |
expect(Liability()).to.be.a Liability | |
it 'constructs with a custodian and a ward', -> | |
a_thing = Thing(); an_exec = Execution() | |
expect(-> Liability an_exec, a_thing).not.to.throwError() | |
li = Liability an_exec, a_thing | |
expect(li).to.be.a Liability | |
expect(li.custodian).to.be an_exec | |
expect(li.ward).to.be a_thing | |
it 'creates write-exclusive (sequential-read) responsibility, by default', -> | |
a_thing = Thing(); an_exec = Execution() | |
li = Liability an_exec, a_thing | |
expect(li.read()).to.be yes | |
expect(li.write()).to.be no | |
it 'can be instructed to construct as write-responsibility, instead', -> | |
a_thing = Thing(); an_exec = Execution() | |
li = Liability an_exec, a_thing, yes | |
expect(li.read()).to.be no | |
expect(li.write()).to.be yes | |
it 'can invoke the relevant adoption-operations on the associated Execution and Thing', -> | |
a_thing = Thing(); an_exec = Execution() | |
li = Liability an_exec, a_thing | |
expect(li.commit()).to.be yes | |
expect(a_thing.belongs_to li).to.be yes | |
expect(an_exec.wards).to.contain li # FIXME: Shouldn't this be an API method? | |
it 'can invoke the relevant relinquishment-operations on the associated Execution and Thing', -> | |
a_thing = Thing(); an_exec = Execution() | |
li = Liability an_exec, a_thing | |
li.commit() | |
expect(a_thing.belongs_to li).to.be yes | |
expect(an_exec.wards).to.contain li # FIXME: Shouldn't this be an API method? | |
expect(li.discard()).to.be yes | |
expect(a_thing.belongs_to li).to.be no | |
expect(an_exec.wards).not.to.contain li # FIXME: Shouldn't this be an API method? | |
describe 'Label', -> # ---- ---- ---- ---- ---- Label | |
it 'contains a String', -> | |
foo = new Label 'foo' | |
expect(foo).to.be.a Thing | |
expect(foo.alien).to.be.a 'string' | |
expect(foo.alien).to.be 'foo' | |
it 'accepts an existing Label instead of an alien, which it then clones', -> | |
orig = new Label 'bar' | |
bar = new Label orig | |
expect(bar).to.be.a Label | |
expect(bar).to.not.be orig | |
expect(bar.alien).to.be.a 'string' | |
expect(bar.alien).to.be 'bar' | |
describe '::clone', -> | |
it 'retains Thing-metadata', -> | |
foo = new Label 'foo' | |
pair = Thing.pair('abc', new Label '123') | |
foo.push pair | |
clone = foo.clone() | |
expect(clone.at(1)).to.be pair | |
it 'copies associated string-data', -> | |
foo = new Label 'foo' | |
clone = foo.clone() | |
expect(clone.alien).to.be 'foo' | |
it 'compares as equal to another Label when they contain the same String', -> | |
foo1 = new Label 'foo' | |
foo2 = new Label 'foo' | |
expect(foo1.compare foo2).to.be true | |
describe 'Execution', -> # ---- ---- ---- ---- ---- Execution | |
# ### Execution: Core functionality ### | |
it 'constructs as a Native instead when passed function-bits', -> | |
expect(new Execution ->).to.be.a Native | |
expect( Execution ->).to.be.a Native | |
it 'constructs as a libspace Execution when passed an expression', -> | |
expect(new Execution new Sequence).to.be.an Execution | |
expect(new Execution new Sequence).not.to.be.a Native | |
expect( Execution new Sequence).to.be.an Execution | |
expect( Execution new Sequence).not.to.be.a Native | |
expect(new Execution ).to.be.an Execution | |
expect(new Execution ).not.to.be.a Native | |
expect( Execution()).to.be.an Execution | |
expect( Execution()).not.to.be.a Native | |
describe '~ Locals storage', -> | |
it 'is provided at construction', -> | |
exe = new Execution | |
expect(exe.locals).to.be.a Thing | |
expect(exe.locals.metadata).to.have.length 2 | |
# Seperate locals-tests into their own suite | |
expect(exe.find 'locals').to.not.be.empty() | |
expect(exe .at(1).valueish()).to.be exe.locals | |
expect(exe .at(1).metadata[2].owns).to.be yes | |
expect(exe.locals.at(1).valueish() ).to.be exe.locals | |
expect(exe.locals.at(1).metadata[2].owns).to.be no | |
describe '~ Position', -> | |
it 'begins in a pristine state', -> | |
expect((new Execution).pristine).to.be yes | |
it 'exists already-completed if created with no instructions', -> | |
expect((new Execution).complete()).to.be yes | |
it 'is set during creation of the Execution', -> | |
seq = new Sequence new Expression | |
expect(-> new Execution seq).to.not.throwException() | |
exec = new Execution seq | |
expect(exec.instructions[0].expression()).to.be seq.at 0 | |
it 'has knowledge of completion', -> | |
ex = new Execution Expression.from ['foo'] | |
expect(ex.complete()).to.be false | |
ex.advance() | |
expect(ex.complete()).to.be false | |
ex.advance() | |
expect(ex.complete()).to.be true | |
it 'is exposed during advancement', -> | |
seq = parse 'abc def' | |
ex = new Execution seq | |
expect(-> ex.current()).to.not.throwException() | |
expect( ex.current()).to.be.a Position | |
expect( ex.current().expression()).to.be seq.at 0 | |
expect( ex.current().valueOf().alien).to.be 'abc' | |
ex.advance() | |
ex.advance new Thing | |
expect( ex.current().expression()).to.be seq.at 0 | |
expect( ex.current().valueOf().alien).to.be 'def' | |
describe '~ The operation queue', -> | |
it 'is initialized', -> | |
ex = new Execution | |
expect(ex.ops).to.be.ok() | |
expect(ex.ops).to.be.an 'array' | |
it 'can be added to', -> | |
ex = new Execution | |
op = new Operation 'foo' | |
expect(ex.ops).to.have.length 0 | |
ex.queue op | |
expect(ex.ops).to.have.length 1 | |
expect(ex.ops[0]).to.be op | |
a_thing = new Thing | |
ex.queue 'bar', a_thing | |
expect(ex.ops).to.have.length 2 | |
expect(ex.ops[1]).to.have.property 'op' | |
expect(ex.ops[1].op).to.be 'bar' | |
expect(ex.ops[1].params).to.contain a_thing | |
it "provides a convenience method to quickly add 'advance' operations", -> | |
ex = new Execution | |
expect(ex.ops).to.have.length 0 | |
a_thing = new Thing | |
ex.respond a_thing | |
expect(ex.ops).to.have.length 1 | |
expect(ex.ops[0]).to.have.property 'op' | |
expect(ex.ops[0].op).to.be 'advance' | |
expect(ex.ops[0].params).to.contain a_thing | |
describe '~ Responsibility tracking', -> | |
it 'stores a set of wards', -> | |
an Execution | |
expect(an.execution.wards).to.be.ok() | |
expect(an.execution.wards).to.be.an 'array' | |
it 'can accept a Liability', -> | |
a Liability, (an Execution), a Thing | |
expect(an.execution.wards).to.be.empty() | |
expect(-> an.execution.accept a.liability ).to.not.throwException() | |
it 'adds an accepted Liability to its wards', -> | |
a Liability, (an Execution), a Thing | |
expect(an.execution.wards).to.be.empty() | |
an.execution.accept a.liability | |
expect(an.execution.wards).to.not.be.empty() | |
expect(an.execution.wards).to.contain a.liability | |
it 'can abjure Liability', -> | |
a Liability, (an Execution), a Thing | |
an.execution.accept a.liability | |
expect(-> an.execution.abjure a.liability ).to.not.throwException() | |
it 'adds an accepted Liability to its wards', -> | |
a Liability, (an Execution), a Thing | |
expect(an.execution.wards).to.be.empty() | |
an.execution.accept a.liability | |
expect(an.execution.wards).to.not.be.empty() | |
an.execution.abjure a.liability | |
expect(an.execution.wards).to.be.empty() | |
# ### Execution: Methods ### | |
describe '::clone', -> | |
it 'creates a new Execution', -> | |
ex = new Execution (new Sequence) | |
expect(-> ex.clone()).to.not.throwException() | |
expect( ex.clone()).to.be.an Execution | |
expect( ex.clone()).to.not.be ex | |
it 'preserves the instructions and results', -> | |
seq1 = new Sequence new Expression | |
seq2 = new Sequence new Expression | |
ex = new Execution seq1 | |
clone1 = ex.clone() | |
expect(clone1.instructions[0].expression()).to.be seq1.at 0 | |
expect(clone1.results).to.not.be ex.results | |
expect(clone1.results).to.eql ex.results | |
ex.instructions[0] = new Position seq2 | |
ex.results.unshift new Label 'intermediate value' | |
clone2 = ex.clone() | |
expect(clone2.instructions[0].expression()).to.be seq2.at 0 | |
expect(clone2.results).to.have.length 2 | |
expect(clone2.results).to.not.be ex.results | |
expect(clone2.results).to.eql ex.results | |
it 'also clones the locals-Thing', -> | |
ex = new Execution (new Sequence) | |
clone = ex.clone() | |
expect(clone.locals).to.not.equal ex.locals | |
expect(clone.find('locals')[0].valueish()).to.equal clone.locals | |
expect(clone.locals.toArray()).to.eql ex.locals.toArray() | |
it 'retains a reference to old locals when cloning', -> | |
ex = new Execution (new Sequence) | |
clone = ex.clone() | |
expect(clone.locals).to.not.equal ex.locals | |
expect(clone.find('locals')[1].valueish()).to.equal ex.locals | |
describe '::advance', -> | |
it "doesn't modify a completed Native", -> | |
completed_alien = new Native | |
expect(completed_alien.complete()).to.be.ok() | |
expect(completed_alien.advance new Thing).to.be undefined | |
it 'flags a modified Native as un-pristine', -> | |
func1 = new Function; func2 = new Function | |
an_alien = new Native func1, func2 | |
an_alien.advance new Thing | |
expect(an_alien.pristine).to.be no | |
it 'advances the bits of a Native', -> | |
func1 = new Function; func2 = new Function | |
an_alien = new Native func1, func2 | |
expect(an_alien.advance new Thing).to.be func1 | |
expect(an_alien.advance new Thing).to.be func2 | |
expect(an_alien.complete()).to.be.ok() | |
it 'completes Executions', -> | |
an_xec = new Execution Expression.from ['something'] | |
an_xec.advance() | |
an_xec.advance() | |
expect(an_xec.complete()).to.be yes | |
it 'does nothing with a completed Execution', -> | |
completed_native = new Execution Expression.from ['something'] | |
completed_native.advance() | |
completed_native.advance() | |
expect(completed_native.advance()).to.be undefined | |
it "doesn't choke on a simple expression", -> | |
an_xec = new Execution Expression.from ['abc', 'def'] | |
expect(-> an_xec.advance()).to.not.throwError() | |
it 'can generate a simple combination against a previous result', -> | |
expr = Expression.from ['something','other']; other = expr.at(1) | |
an_xec = new Execution expr | |
an_xec.advance() | |
something = new Thing | |
combo = an_xec.advance something | |
expect(combo.subject).to.be something | |
expect(combo.message).to.be other | |
it 'implicitly combines against locals at the beginning of an Execution', -> | |
expr = Expression.from ['something']; something = expr.at(0) | |
an_xec = new Execution expr | |
combo = an_xec.advance() | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be something | |
it 'will dive into sub-expressions, again implicitly combining against locals', -> | |
expr = Expression.from ['something', ['other']]; other = expr.at(1).at(0,0) | |
an_xec = new Execution expr | |
c1 = an_xec.advance() | |
something = (new Thing).rename 'something' | |
combo = an_xec.advance something | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be other | |
it "retains the previous result at the parent's level, | |
and juxtaposes against that when exiting", -> | |
expr = Expression.from ['something', ['other']] | |
an_xec = new Execution expr | |
an_xec.advance() | |
something = new Thing | |
an_xec.advance something | |
other = new Object | |
combo = an_xec.advance other | |
expect(combo.subject).to.be something | |
expect(combo.message).to.be other | |
it 'descends into multiple levels of nested-immediate sub-expressions', -> | |
expr = Expression.from ['something', [[['other']]]] | |
an_xec = new Execution expr | |
an_xec.advance() | |
# ~locals <- 'something' | |
something = new Thing | |
an_xec.advance something | |
# ~locals <- 'other' | |
other = new Thing | |
combo = an_xec.advance other | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be other | |
# ~locals <- other | |
meta_other = new Thing | |
combo = an_xec.advance meta_other | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be meta_other | |
# ~locals <- <meta-other> | |
meta_meta_other = new Thing | |
combo = an_xec.advance meta_meta_other | |
expect(combo.subject).to.be something | |
expect(combo.message).to.be meta_meta_other | |
# something <- <meta-meta-other> | |
it 'handles an *immediate* sub-expression', -> | |
expr = Expression.from [['something'], 'other']; other = expr.at(1) | |
an_xec = new Execution expr | |
an_xec.advance() | |
# ~locals <- 'something' | |
something = new Thing | |
combo = an_xec.advance something | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be something | |
# ~locals <- something | |
meta_something = new Thing | |
combo = an_xec.advance meta_something | |
expect(combo.subject).to.be meta_something | |
expect(combo.message).to.be other | |
# <meta-something> <- 'other' | |
it 'descends into multiple levels of *immediate* nested sub-expressions', -> | |
expr = Expression.from [[[['other']]]] | |
an_xec = new Execution expr | |
an_xec.advance() | |
# ~locals <- 'other' | |
other = new Thing | |
combo = an_xec.advance other | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be other | |
# ~locals <- other | |
meta_other = new Thing | |
combo = an_xec.advance meta_other | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be meta_other | |
# ~locals <- <meta-other> | |
meta_meta_other = new Thing | |
combo = an_xec.advance meta_meta_other | |
expect(combo.subject).to.be null | |
expect(combo.message).to.be meta_meta_other | |
# ~locals <- <meta-meta-other> | |
# ### Execution: Combination-receiver ### | |
describe '~ The default `receiver`', -> | |
caller = undefined; receiver = undefined | |
beforeEach -> | |
caller = new Execution | |
receiver = Execution::receiver.clone() | |
it 'clones the subject,', -> | |
an_exec = new Execution; something = new Thing | |
params = Execution.create_params caller, an_exec, something | |
sinon.spy an_exec, 'clone' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(an_exec.clone).was.called() | |
it 'resumes that clone,', -> | |
an_exec = new Execution; something = new Thing | |
params = Execution.create_params caller, an_exec, something | |
sinon.spy an_exec, 'clone' | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
clone = an_exec.clone.returnValues[0] | |
expect(clone.ops).to.have.length 1 | |
expect(clone.ops[0].op).to.be 'advance' | |
expect(clone.ops[0].params).to.contain something | |
it 'does not re-stage the caller', -> | |
an_exec = new Execution; something = new Thing | |
params = Execution.create_params caller, an_exec, something | |
bit = receiver.advance params | |
bit.apply receiver, [params] | |
expect(caller.ops).to.have.length 0 | |
describe 'Native', -> # ---- ---- ---- ---- ---- Native | |
# ### Native: Core functionality & methods ### | |
it 'constructs with a series of procedure-bits', -> | |
a = (->); b = (->); c = (->) | |
expect(-> new Execution a, b, c).to.not.throwException() | |
expect( new Execution a, b, c).to.be.a Native | |
expect( (new Execution a, b, c).bits).to.have.length 3 | |
expect( (new Execution a, b, c).bits).to.eql [a, b, c] | |
describe '::complete', -> | |
it 'knows whether the Native is complete', -> | |
ex = new Execution -> | |
expect(ex.complete()).to.be false | |
ex.bits.length = 0 | |
expect(ex.complete()).to.be true | |
describe '::clone', -> | |
it 'creates a new Native', -> | |
ex = new Execution -> | |
expect(-> ex.clone()).to.not.throwException() | |
expect( ex.clone()).to.be.a Native | |
it '... that has the same bits after cloning', -> | |
funcs = | |
one: -> | |
two: -> | |
three: -> | |
ex = new Execution funcs.one, funcs.two, funcs.three | |
clone = ex.clone() | |
expect(clone.bits).to.not.be ex.bits | |
expect(clone.bits).to.eql [funcs.one, funcs.two, funcs.three] | |
# FIXME: Why did I expect this to behave differently when it was a `Native`!? | |
it.skip 'shares locals with created clones', -> | |
ex = new Execution -> | |
clone = ex.clone() | |
expect(clone.locals).to.equal ex.locals | |
# ### Native: Utility / convenience methods and functions ### | |
describe '.synchronous', -> | |
synchronous = Native.synchronous | |
it 'accepts a function', -> | |
expect( synchronous).to.be.ok() | |
expect(-> synchronous ->).to.not.throwException() | |
it 'creates a new Native', -> | |
expect(synchronous ->).to.be.a Native | |
it 'adds bits corresponding to the arity of the function', -> | |
expect( (synchronous (a, b)->) .bits).to.have.length 3 | |
expect( (synchronous (a, b, c, d)->) .bits).to.have.length 5 | |
describe '~ Produces bits that,', -> | |
a = null | |
beforeEach -> a = | |
caller: new Execution | |
thing: new Label 'foo' | |
call = (it, response)-> | |
bit = it.advance response | |
bit.call it, response | |
it 'are Functions', -> | |
exe = synchronous (a, b, c)-> | |
expect(exe.bits[0]).to.be.a Function | |
expect(exe.bits[1]).to.be.a Function | |
expect(exe.bits[2]).to.be.a Function | |
expect(exe.bits[3]).to.be.a Function | |
it 'mostly expect a caller and value', -> | |
exe = synchronous (a, b, c)-> | |
expect(exe.bits[1]).to.have.length 2 | |
expect(exe.bits[2]).to.have.length 2 | |
it 'the first of which expects only value-to-become-caller', -> | |
exe = synchronous (a, b, c)-> | |
expect(exe.bits[0]).to.have.length 1 | |
# FIXME: lodash's `_.partial` doesn't export a correct `length`, nor does it provide | |
# access to the original, wrapped `Function`; I don't know how to test this. | |
it.skip 'the last of which *additionally* expects several other arguments', -> | |
body = (a, b, c)-> | |
exe = synchronous body | |
expect(exe.bits[3]).to.have.length 4 + 1 | |
it 'can be invoked with a caller', -> | |
exe = synchronous (a, b, c)-> | |
sinon.spy a.caller, 'queue' | |
expect(-> call exe, a.caller).to.not.throwException() | |
expect(a.caller.queue).was.calledWith __ params: [exe] | |
it 'can be invoked with further parameters', -> | |
exe = synchronous (a, b, c)-> | |
sinon.spy a.caller, 'queue' | |
call exe, a.caller | |
expect(-> call exe, a.thing).to.not.throwException() | |
expect(-> call exe, a.thing).to.not.throwException() | |
expect(a.caller.queue).was.calledWith __ params: [exe] | |
expect(a.caller.queue).was.calledThrice() | |
it 'are each provided the `caller` passed to the first bit', -> | |
some_function = sinon.spy (a, b, c)-> | |
exe = synchronous some_function | |
exe.bits = exe.bits.map (bit)-> sinon.spy bit | |
bits = exe.bits.slice() | |
call exe, a.caller | |
call exe, new Label 123 | |
call exe, new Label 456 | |
call exe, new Label 789 | |
assert bits[1].calledWith a.caller | |
assert bits[2].calledWith a.caller | |
assert bits[3].calledWith a.caller | |
it 'resume the `caller` after consuming an argument', -> | |
exe = synchronous (a, b, c)-> | |
queue = sinon.spy a.caller, 'queue' | |
call exe, a.caller | |
expect(queue.callCount).to.be 1 | |
expect(queue.thisValues[0]).to.be a.caller | |
expect(queue.args[0][0].params).to.contain exe | |
call exe, new Label 123 | |
expect(queue.callCount).to.be 2 | |
expect(queue.thisValues[1]).to.be a.caller | |
expect(queue.args[1][0].params).to.contain exe | |
call exe, new Label 456 | |
expect(queue.callCount).to.be 3 | |
expect(queue.thisValues[2]).to.be a.caller | |
expect(queue.args[2][0].params).to.contain exe | |
#call exe, new Label 456 | |
it 'do not re-stage the `caller` after all coproduction if there is no result', -> | |
result = new Label "A result!" | |
exe = synchronous (a, b)-> | |
queue = sinon.spy a.caller, 'queue' | |
call exe, a.caller | |
expect(queue.callCount).to.be 1 | |
expect(queue.thisValues[0]).to.be a.caller | |
expect(queue.args[0][0].params).to.contain exe | |
call exe, new Label 123 | |
expect(queue.callCount).to.be 2 | |
expect(queue.thisValues[1]).to.be a.caller | |
expect(queue.args[1][0].params).to.contain exe | |
call exe, new Label 456 | |
expect(queue.callCount).to.not.be 3 | |
it 're-stage the `caller` after all coproduction if a result is returned', -> | |
result = new Label "A result!" | |
exe = synchronous (a)-> return result | |
queue = sinon.spy a.caller, 'queue' | |
call exe, a.caller | |
expect(queue.callCount).to.be 1 | |
expect(queue.thisValues[0]).to.be a.caller | |
expect(queue.args[0][0].params).to.contain exe | |
call exe, new Label 123 | |
expect(queue.callCount).to.be 2 | |
expect(queue.thisValues[1]).to.be a.caller | |
expect(queue.args[1][0].params).to.contain result | |
it 're-stage the `caller` immediately if no arguments is required', -> | |
queue = sinon.spy a.caller, 'queue' | |
result = new Label "A result!" | |
exe = synchronous -> return result | |
call exe, a.caller | |
expect(queue.callCount).to.be 1 | |
expect(queue.thisValues[0]).to.be a.caller | |
expect(queue.args[0][0].params).to.contain result | |
it 'call the passed function exactly once, when exhausted', -> | |
some_function = sinon.spy (a, b, c)-> | |
exe = synchronous some_function | |
call exe, a.caller | |
call exe, new Label 123 | |
call exe, new Label 456 | |
call exe, new Label 789 | |
expect(some_function).was.calledOnce() | |
it 'collect individually passed arguments into arguments to the passed function', -> | |
some_function = sinon.spy (a, b, c)-> | |
exe = synchronous some_function | |
things = | |
first: new Label 123 | |
second: new Label 456 | |
third: new Label 789 | |
call exe, a.caller | |
call exe, things.first | |
call exe, things.second | |
call exe, things.third | |
expect(some_function).was.calledWithExactly things.first, things.second, things.third | |
it 'inject context into the passed function', -> | |
some_function = sinon.spy (arg)-> | |
exe = synchronous some_function | |
call exe, a.caller | |
call exe, a.thing | |
expect(some_function.firstCall.thisValue).to.have.property 'caller' | |
expect(some_function.firstCall.thisValue.caller).to.be.an Execution | |
expect(some_function.firstCall.thisValue.caller).to.be a.caller | |
expect(some_function.firstCall.thisValue).to.have.property 'execution' | |
expect(some_function.firstCall.thisValue.execution).to.be.an Execution | |
expect(some_function.firstCall.thisValue.execution).to.be exe | |
# ### Execution: Supporting types ### | |
describe 'Operation', -> # ---- ---- ---- ---- ---- Operation | |
it 'consists of of a stringly-typed operation,', -> | |
expect(-> new Operation 'foo').to.not.throwError() | |
it 'can have some arguments', -> | |
expect(-> new Operation 'foo', new Thing, new Thing).to.not.throwError() | |
it 'maintains a global map of known operations', -> | |
expect(Operation.operations).to.be.ok() | |
expect(Operation.operations).to.be.an 'object' | |
it 'can be told to register new operation-types', -> | |
expect( Operation.register).to.be.ok() | |
expect( Operation.register).to.be.a 'function' | |
[ops, Operation.operations] = [Operation.operations, new Array] | |
an_op = new Function | |
expect(-> Operation.register 'op', an_op).to.not.throwError() | |
expect( Operation.operations).to.have.key 'op' | |
expect( Operation.operations['op']).to.be an_op | |
Operation.operations = ops | |
it 'applies the body of the operation against a passed Execution', -> | |
[ops, Operation.operations] = [Operation.operations, new Array] | |
Operation.register 'op', sinon.spy() | |
an_exec = new Execution | |
it = new Operation 'op' | |
it.perform an_exec | |
expect(Operation.operations['op']).was.calledOn an_exec | |
Operation.operations = ops | |
# ### Execution: Available operations ### | |
describe "~ The 'advance' operation", -> # ---- ---- ---- ---- ---- Ops['advance'] | |
it 'exists', -> | |
expect(Operation.operations['advance']).to.be.ok() | |
expect(Operation.operations['advance']).to.be.a 'function' | |
it 'advances the execution', -> | |
an_exec = new Native -> | |
sinon.spy an_exec, 'advance' | |
op = new Operation 'advance' | |
op.perform an_exec | |
expect(an_exec.advance).was.calledOnce() | |
it 'does nothing if the execution is complete', -> | |
an_exec = new Native | |
sinon.spy an_exec, 'advance' | |
op = new Operation 'advance' | |
op.perform an_exec | |
expect(an_exec.advance).was.notCalled() | |
it "calls a Native's next bit,", -> | |
bit = sinon.spy() | |
an_exec = new Native bit | |
a_thing = new Thing | |
op = new Operation 'advance', a_thing | |
op.perform an_exec | |
expect(bit).was.calledOnce() | |
expect(bit).was.calledWith a_thing | |
it "clones an Combination's receiver", sinon.test -> | |
a_subject = new Label 'foo' | |
an_exec = new Execution parse '_ bar' | |
a_receiver = new Native | |
@spy a_subject.receiver, 'clone' | |
an_exec.advance() | |
op = new Operation 'advance', new Thing | |
op.perform an_exec | |
expect(a_subject.receiver.clone).was.calledOnce() | |
expect(a_subject.receiver.ops).to.be.empty() | |
it 'queues a further advancement operation for that receiver-clone', sinon.test -> | |
an_exec = new Execution (new Sequence) | |
a_subject = new Thing; a_message = new Thing | |
a_receiver = new Native | |
@stub(an_exec, 'advance').returns new Combination a_subject, a_message | |
@stub(a_subject.receiver, 'clone').returns a_receiver | |
op = new Operation 'advance', new Thing | |
op.perform an_exec | |
expect(a_receiver.ops).to.not.be.empty() | |
expect(a_receiver.ops[0].params).to.not.be.empty() | |
params = a_receiver.ops[0].params[0] | |
expect(params.at 0).to.be an_exec | |
expect(params.at 1).to.be a_subject | |
expect(params.at 2).to.be a_message | |
it 'uses locals at the edges of expressions', sinon.test -> | |
@spy Label::receiver, 'clone' | |
an_exec = new Execution parse 'foo []' | |
op = new Operation 'advance', new Thing | |
op.perform an_exec | |
receiver = Label::receiver.clone.firstCall.returnValue | |
expect(receiver.ops.length).to.be.above 0 | |
expect(receiver.ops[0].params).to.not.be.empty() | |
params = receiver.ops[0].params[0] | |
expect(params.at 1).to.be an_exec.locals | |
describe "~ The 'adopt' operation", -> # ---- ---- ---- ---- ---- Ops['adopt'] | |
it 'exists', -> | |
expect(Operation.operations['adopt']).to.be.ok() | |
expect(Operation.operations['adopt']).to.be.a 'function' | |
it 'adopts responsibility if available', -> | |
a Thing; an Execution, parse 'foo []' | |
a Liability, an.execution, a.thing | |
op = new Operation 'adopt', a.liability | |
expect(-> op.perform an.execution).to.not.throwException() | |
expect(a.thing.belongs_to a.liability).to.be yes | |
expect(an.execution.wards).to.contain a.liability # FIXME: Shouldn't this be an API method? | |
it 'returns truthy if it was able to take ownership', -> | |
a Thing; an Execution, parse 'foo []' | |
a Liability, an.execution, a.thing | |
op = new Operation 'adopt', a.liability | |
expect(op.perform an.execution).to.be yes | |
it.skip 'returns falsey if ownership-taking failed', -> | |
a Thing; an Execution, parse 'foo []' | |
a Liability, an.execution, a.thing | |
another Liability, (another Execution), a.thing, 'write' | |
another.liability.commit() | |
li = Liability an.execution, a.thing, 'write' | |
op = new Operation 'adopt', li | |
expect(op.perform an.execution).to.be no | |
expect(a.thing.belongs_to li).to.be no | |
expect(an.execution.wards).not.to.contain li # FIXME: Shouldn't this be an API method? |