Skip to content

Commit 710987a

Browse files
committed
thenable active expressions
SQUASHED: AUTO-COMMIT-src-client-reactive-active-expression-active-expression.js,AUTO-COMMIT-src-client-reactive-test-active-expression-utility-methods.spec.js,AUTO-COMMIT-src-components-tools-lively-editor.js,
1 parent 774b087 commit 710987a

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

src/client/reactive/active-expression/active-expression.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,21 @@ export class BaseActiveExpression {
457457
if(value !== undefined)this.meta({isMeta : value});
458458
else return this.meta().has('isMeta') && this.meta().get('isMeta');
459459
}
460+
461+
/*MD ## Iterators and Utility Methods MD*/
462+
nextValue() {
463+
return new Promise((resolve, reject) => {
464+
const callback = value => {
465+
this.offChange(callback);
466+
resolve(value);
467+
};
468+
this.onChange(callback);
469+
});
470+
}
471+
472+
then(...args) {
473+
return this.nextValue().then(...args);
474+
}
460475
}
461476

462477
export function aexpr(func, ...args) {
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"enable aexpr";
2+
3+
import chai, {expect} from 'src/external/chai.js';
4+
import sinon from 'src/external/sinon-3.2.1.js';
5+
import sinonChai from 'src/external/sinon-chai.js';
6+
chai.use(sinonChai);
7+
8+
describe('Iterators and Utility Methods for Active Expressions', () => {
9+
it("chainable", () => {
10+
let obj = {a: 2},
11+
spy = sinon.spy();
12+
13+
aexpr(() => obj.a > 5)
14+
.onBecomeFalse(spy)
15+
.onBecomeTrue(spy)
16+
.onBecomeFalse(spy)
17+
.onBecomeTrue(spy);
18+
19+
expect(spy).to.be.calledTwice;
20+
});
21+
22+
it("flank up", () => {
23+
let obj = {a: 2},
24+
spy = sinon.spy();
25+
26+
let axp = aexpr(() => obj.a > 5);
27+
axp.onBecomeTrue(spy);
28+
expect(spy).not.to.be.called;
29+
30+
obj.a = 10;
31+
expect(spy).to.be.calledOnce;
32+
33+
obj.a = 0;
34+
expect(spy).to.be.calledOnce;
35+
36+
obj.a = 10;
37+
expect(spy).to.be.calledTwice;
38+
});
39+
40+
it("immediately triggers onBecomeTrue", () => {
41+
let obj = {a: 7},
42+
spy = sinon.spy();
43+
44+
let axp = aexpr(() => obj.a > 5);
45+
axp.onBecomeTrue(spy);
46+
expect(spy).to.be.calledOnce;
47+
48+
obj.a = 0;
49+
expect(spy).to.be.calledOnce;
50+
51+
obj.a = 10;
52+
expect(spy).to.be.calledTwice;
53+
});
54+
55+
it("flank down", () => {
56+
let obj = {a: 2},
57+
spy = sinon.spy();
58+
59+
let axp = aexpr(() => obj.a > 0);
60+
axp.onBecomeFalse(spy);
61+
expect(spy).not.to.be.called;
62+
63+
obj.a = -2;
64+
expect(spy).to.be.calledOnce;
65+
66+
obj.a = 2;
67+
expect(spy).to.be.calledOnce;
68+
69+
obj.a = -2;
70+
expect(spy).to.be.calledTwice;
71+
});
72+
73+
it("immediately triggers onBecomeFalse", () => {
74+
let obj = {a: -2},
75+
spy = sinon.spy();
76+
77+
let axp = aexpr(() => obj.a > 0);
78+
axp.onBecomeFalse(spy);
79+
expect(spy).to.be.calledOnce;
80+
81+
obj.a = 2;
82+
expect(spy).to.be.calledOnce;
83+
84+
obj.a = -2;
85+
expect(spy).to.be.calledTwice;
86+
});
87+
88+
describe('nextValue', () => {
89+
it("aexprs define nextValue", () => {
90+
expect(aexpr(() => {})).to.respondTo('nextValue');
91+
});
92+
93+
it("nextValue returns a Promise", () => {
94+
expect(aexpr(() => {}).nextValue()).to.be.an.instanceof(Promise);
95+
});
96+
97+
it("nextValue resolves on first change of monitored expression", async () => {
98+
let obj = {a: 1},
99+
spy = sinon.spy();
100+
101+
let axp = aexpr(() => obj.a);
102+
103+
async function waitingForChange() {
104+
const v = await axp.nextValue();
105+
spy(v);
106+
}
107+
108+
waitingForChange();
109+
110+
expect(spy).not.to.be.called;
111+
112+
obj.a = 42;
113+
// await is resolved as a separate micro task
114+
expect(spy).not.to.be.called;
115+
116+
await lively.sleep(10);
117+
118+
expect(spy).to.be.calledOnce;
119+
expect(spy).to.be.calledWith(42);
120+
spy.reset();
121+
122+
obj.a = 43;
123+
124+
await lively.sleep(10);
125+
expect(spy).not.to.be.called;
126+
});
127+
128+
describe('thenable AExprs', () => {
129+
130+
it("Active Expressions are thenables", async () => {
131+
let obj = {a: 1},
132+
spy = sinon.spy();
133+
134+
let axp = aexpr(() => obj.a);
135+
136+
async function waitingForChange() {
137+
const v = await axp;
138+
spy(v);
139+
}
140+
141+
waitingForChange();
142+
143+
expect(spy).not.to.be.called;
144+
145+
// the following await is crutial, as the `.then` on axp is executed
146+
// in waitingForChange in its own micro task only AFTER this
147+
// synchronous part is over!!!
148+
await lively.sleep(10);
149+
150+
obj.a = 17;
151+
// await is resolved as a separate micro task
152+
expect(spy).not.to.be.called;
153+
154+
await lively.sleep(10);
155+
156+
expect(spy).to.be.calledOnce;
157+
expect(spy).to.be.calledWith(17);
158+
spy.reset();
159+
160+
obj.a = 43;
161+
expect(spy).not.to.be.called;
162+
});
163+
164+
});
165+
166+
});
167+
});

src/components/tools/lively-editor.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ export default class Editor extends Morph {
967967
}
968968

969969
async enableAnnotations() {
970+
debugger
970971
await this.loadAnnotations(this.getText(), this.lastVersion)
971972
lively.removeEventListener("annotations", this)
972973
lively.addEventListener("annotations", this, "loaded-file", async evt => {

0 commit comments

Comments
 (0)