1 | describe("Scope", function() {
|
2 | var scope, fallback;
|
3 |
|
4 | before(function() {
|
5 | scope = new Temple.Scope();
|
6 | });
|
7 |
|
8 | beforeEach(function() {
|
9 | scope.set("foo", "bar");
|
10 | fallback = new Temple.Model({ bar: "baz" });
|
11 | scope.addModel(fallback);
|
12 | });
|
13 |
|
14 | afterEach(function() {
|
15 | scope.stopObserving();
|
16 | scope.removeModel(fallback);
|
17 | fallback = null;
|
18 | });
|
19 |
|
20 | it("creates a model on constructor if a model isn't passed", function() {
|
21 | expect(scope.models[0]).to.be.instanceof(Temple.Model);
|
22 | });
|
23 |
|
24 | it("get(path) executes function value iff value at path is function", function() {
|
25 | scope.set("foo", function() {
|
26 | expect(this).to.equal(scope);
|
27 | return "Hello World";
|
28 | });
|
29 |
|
30 | expect(scope.get("foo")).to.equal("Hello World");
|
31 | });
|
32 |
|
33 | it("adds fallback model", function() {
|
34 | expect(scope.models[1]).to.deep.equal(fallback);
|
35 | });
|
36 |
|
37 | it("removes fallback model", function() {
|
38 | scope.removeModel(fallback);
|
39 | expect(scope.models).to.have.length(1);
|
40 | });
|
41 |
|
42 | it("scope returns fallback value at path iff model value at path is undefined", function() {
|
43 | expect(scope.get("foo")).to.equal("bar");
|
44 | expect(scope.get("bar")).to.equal("baz");
|
45 | });
|
46 |
|
47 | it("if path is prefixed with `this`, model returns exact value at path", function() {
|
48 | expect(scope.get("this.foo")).to.equal("bar");
|
49 | expect(scope.get("this.bar")).to.be.undefined;
|
50 | });
|
51 |
|
52 | describe("#observe()", function() {
|
53 |
|
54 | it("successfully adds observer", function() {
|
55 | var fn = function(){};
|
56 | scope.observe("foo", fn);
|
57 | expect(scope._observers.some(function(o) {
|
58 | return o.fn === fn;
|
59 | })).to.be.ok;
|
60 | });
|
61 |
|
62 | it("successfully removes observer", function() {
|
63 | var fn = function() { throw new Error("Observer wasn't removed!"); }
|
64 | scope.observe("foo", fn);
|
65 |
|
66 | expect(function() {
|
67 | scope.set("foo", "baz");
|
68 | }).to.throw(Error);
|
69 |
|
70 | scope.stopObserving("foo", fn);
|
71 | scope.set("foo", "bar");
|
72 | });
|
73 |
|
74 | it("calling stopObserving() without arguments clears all observers", function() {
|
75 | scope.observe("foo", function() { throw new Error("Observer wasn't removed!"); });
|
76 | expect(scope._observers).to.have.length(1);
|
77 | scope.stopObserving();
|
78 | scope.set("foo", "baz");
|
79 | expect(scope._observers).to.have.length(0);
|
80 | });
|
81 |
|
82 | it("calling stopObserving(path) clears all observers with matching path", function() {
|
83 | var fn = function() { throw new Error("Observer wasn't removed!"); }
|
84 | scope.observe("foo", fn);
|
85 | scope.observe("foo", fn);
|
86 | scope.observe("bar", fn);
|
87 | expect(scope._observers).to.have.length(3);
|
88 | scope.stopObserving("foo");
|
89 | scope.set("foo", "baz");
|
90 | expect(scope._observers).to.have.length(1);
|
91 | });
|
92 |
|
93 | it("calling stopObserving(null, fn) clears all observers with matching function", function() {
|
94 | var fn = function() { throw new Error("Observer wasn't removed!"); }
|
95 | scope.observe("foo", fn);
|
96 | scope.observe("bar", fn);
|
97 | scope.observe("baz", function(){});
|
98 | expect(scope._observers).to.have.length(3);
|
99 | scope.stopObserving(null, fn);
|
100 | scope.set("foo", "baz");
|
101 | expect(scope._observers).to.have.length(1);
|
102 | });
|
103 |
|
104 | it("observes nothing when nothing changes", function() {
|
105 | scope.observe("foo", function() { throw new Error("A change was observed."); });
|
106 | scope.set("foo", "bar");
|
107 | });
|
108 |
|
109 | it("observes static path changes", function() {
|
110 | var seen = false;
|
111 | scope.observe("foo.bar", function(chg) {
|
112 | expect(this).to.equal(scope);
|
113 |
|
114 | expect(chg).to.deep.equal({
|
115 | model: scope.getModel("foo.bar"),
|
116 | previousModel: scope.getModel("foo.bar"),
|
117 | path: "foo.bar",
|
118 | type: "add",
|
119 | value: "baz",
|
120 | oldValue: undefined
|
121 | });
|
122 |
|
123 | seen = true;
|
124 | });
|
125 |
|
126 | scope.set("foo", { bar: "baz" });
|
127 | expect(seen).to.be.ok;
|
128 | });
|
129 |
|
130 | it("observes changes only once", function() {
|
131 | var seen = 0;
|
132 | scope.observe("foo", function() { seen++; });
|
133 | scope.set("foo", { bar: "baz" });
|
134 | expect(seen).to.equal(1);
|
135 | });
|
136 |
|
137 | it("observes unset", function() {
|
138 | var seen = false;
|
139 | scope.observe("foo", function(chg) {
|
140 | expect(chg).to.deep.equal({
|
141 | model: scope.getModel("foo"),
|
142 | previousModel: scope.getModel("foo"),
|
143 | path: "foo",
|
144 | type: "delete",
|
145 | value: undefined,
|
146 | oldValue: "bar"
|
147 | });
|
148 |
|
149 | seen = true;
|
150 | });
|
151 |
|
152 | scope.unset("foo");
|
153 | expect(seen).to.be.ok;
|
154 | });
|
155 |
|
156 | it("calling get() in an observer returns the new value", function() {
|
157 | var seen = false;
|
158 | scope.observe("foo.bar", function(chg) {
|
159 | expect(this.get(chg.path)).to.equal(chg.value);
|
160 | seen = true;
|
161 | });
|
162 |
|
163 | scope.set("foo.bar", "baz");
|
164 | expect(seen).to.be.ok;
|
165 | });
|
166 |
|
167 | it("observes empty path", function() {
|
168 | var seen = false;
|
169 | scope.observe("", function(chg) {
|
170 | expect(chg).to.deep.equal({
|
171 | model: scope.getModel(),
|
172 | previousModel: scope.getModel(),
|
173 | path: "",
|
174 | type: "update",
|
175 | value: "foo",
|
176 | oldValue: { foo: "bar" }
|
177 | });
|
178 |
|
179 | seen = true;
|
180 | });
|
181 |
|
182 | scope.set([], "foo");
|
183 | expect(seen).to.be.ok;
|
184 | });
|
185 |
|
186 | it("observes dynamic path: *", function() {
|
187 | var seen = false;
|
188 | scope.observe("*", function(chg) {
|
189 | expect(chg).to.deep.equal({
|
190 | model: scope.getModel("foo"),
|
191 | previousModel: scope.getModel("foo"),
|
192 | path: "foo",
|
193 | type: "update",
|
194 | value: { bar: "baz" },
|
195 | oldValue: "bar"
|
196 | });
|
197 |
|
198 | seen = true;
|
199 | });
|
200 |
|
201 | scope.set("foo", { bar: "baz" });
|
202 | expect(seen).to.be.ok;
|
203 | });
|
204 |
|
205 | it("observes dynamic path: *.bar.baz", function() {
|
206 | var seen = false;
|
207 | scope.observe("*.bar.baz", function(chg) {
|
208 | expect(chg).to.deep.equal({
|
209 | model: scope.getModel("foo.bar.baz"),
|
210 | previousModel: scope.getModel("foo.bar.baz"),
|
211 | path: "foo.bar.baz",
|
212 | type: "add",
|
213 | value: "buz",
|
214 | oldValue: undefined
|
215 | });
|
216 |
|
217 | seen = true;
|
218 | });
|
219 |
|
220 | scope.set("foo.bar.baz", "buz");
|
221 | expect(seen).to.be.ok;
|
222 | });
|
223 |
|
224 | it("observes dynamic path: foo.*.baz", function() {
|
225 | var seen = false;
|
226 | scope.observe("foo.*.baz", function(chg) {
|
227 | expect(chg).to.deep.equal({
|
228 | model: scope.getModel("foo.bar.baz"),
|
229 | previousModel: scope.getModel("foo.bar.baz"),
|
230 | path: "foo.bar.baz",
|
231 | type: "add",
|
232 | value: "buz",
|
233 | oldValue: undefined
|
234 | });
|
235 |
|
236 | seen = true;
|
237 | });
|
238 |
|
239 | scope.set("foo.bar.baz", "buz");
|
240 | expect(seen).to.be.ok;
|
241 | });
|
242 |
|
243 | it("observes dynamic path: foo.bar.*", function() {
|
244 | var seen = false;
|
245 | scope.observe("foo.bar.*", function(chg) {
|
246 | expect(chg).to.deep.equal({
|
247 | model: scope.getModel("foo.bar.baz"),
|
248 | previousModel: scope.getModel("foo.bar.baz"),
|
249 | path: "foo.bar.baz",
|
250 | type: "add",
|
251 | value: "buz",
|
252 | oldValue: undefined
|
253 | });
|
254 |
|
255 | seen = true;
|
256 | });
|
257 |
|
258 | scope.set("foo.bar.baz", "buz");
|
259 | expect(seen).to.be.ok;
|
260 | });
|
261 |
|
262 | it("observes dynamic path: **", function() {
|
263 | var seen = false;
|
264 | scope.set("foo", { bar: "baz" });
|
265 |
|
266 | scope.observe("**", function(chg) {
|
267 | expect(chg).to.deep.equal({
|
268 | model: scope.getModel("foo.bar"),
|
269 | previousModel: scope.getModel("foo.bar"),
|
270 | path: "foo.bar",
|
271 | type: "update",
|
272 | value: { baz: "buz" },
|
273 | oldValue: "baz"
|
274 | });
|
275 |
|
276 | seen = true;
|
277 | });
|
278 |
|
279 | scope.set("foo.bar", { baz: "buz" });
|
280 | expect(seen).to.be.ok;
|
281 | });
|
282 |
|
283 | it("observes dynamic path: **.baz", function() {
|
284 | var seen = false;
|
285 |
|
286 | scope.observe("**.baz", function(chg) {
|
287 | expect(chg).to.deep.equal({
|
288 | model: scope.getModel("foo.bar.baz"),
|
289 | previousModel: scope.getModel("foo.bar.baz"),
|
290 | path: "foo.bar.baz",
|
291 | type: "add",
|
292 | value: "buz",
|
293 | oldValue: undefined
|
294 | });
|
295 |
|
296 | seen = true;
|
297 | });
|
298 |
|
299 | scope.set("foo.bar.baz", "buz");
|
300 | expect(seen).to.be.ok;
|
301 | });
|
302 |
|
303 | it("observes dynamic path: foo.**.baz", function() {
|
304 | var seen = false;
|
305 | scope.observe("foo.**.baz", function(chg) {
|
306 | expect(chg).to.deep.equal({
|
307 | model: scope.getModel("foo.bar.bun.baz"),
|
308 | previousModel: scope.getModel("foo.bar.bun.baz"),
|
309 | path: "foo.bar.bun.baz",
|
310 | type: "add",
|
311 | value: "buz",
|
312 | oldValue: undefined
|
313 | });
|
314 |
|
315 | seen = true;
|
316 | });
|
317 |
|
318 | scope.set("foo.bar.bun.baz", "buz");
|
319 | expect(seen).to.be.ok;
|
320 | });
|
321 |
|
322 | it("observes dynamic path: foo.**", function() {
|
323 | var seen = false;
|
324 | scope.set("foo.bar.baz", "buz");
|
325 |
|
326 | scope.observe("foo.**", function(chg) {
|
327 | expect(chg).to.deep.equal({
|
328 | model: scope.getModel("foo.bar.baz"),
|
329 | previousModel: scope.getModel("foo.bar.baz"),
|
330 | path: "foo.bar.baz",
|
331 | type: "update",
|
332 | value: "bun",
|
333 | oldValue: "buz"
|
334 | });
|
335 |
|
336 | seen = true;
|
337 | });
|
338 |
|
339 | scope.set("foo.bar.baz", "bun");
|
340 | expect(seen).to.be.ok;
|
341 | });
|
342 |
|
343 | it("observing path foo.** captures changes at path foo", function() {
|
344 | var seen = false;
|
345 | scope.observe("foo.**", function(chg) {
|
346 | expect(chg).to.deep.equal({
|
347 | model: scope.getModel("foo"),
|
348 | previousModel: scope.getModel("foo"),
|
349 | path: "foo",
|
350 | type: "update",
|
351 | value: "buz",
|
352 | oldValue: "bar"
|
353 | });
|
354 |
|
355 | seen = true;
|
356 | });
|
357 |
|
358 | scope.set("foo", "buz");
|
359 | expect(seen).to.be.ok;
|
360 | });
|
361 |
|
362 | it("observes changes to fallback value", function() {
|
363 | var seen = false;
|
364 |
|
365 | scope.observe("bar", function(chg) {
|
366 | expect(this).to.equal(scope);
|
367 |
|
368 | expect(chg).to.deep.equal({
|
369 | model: fallback.getModel("bar"),
|
370 | previousModel: fallback.getModel("bar"),
|
371 | path: "bar",
|
372 | type: "update",
|
373 | value: "bam",
|
374 | oldValue: "baz"
|
375 | });
|
376 |
|
377 | seen = true;
|
378 | });
|
379 |
|
380 | fallback.set("bar", "bam");
|
381 | expect(seen).to.be.ok;
|
382 | });
|
383 |
|
384 | it("observes changes to local value when the value was found in fallback", function() {
|
385 | var seen = false;
|
386 |
|
387 | scope.observe("bar", function(chg) {
|
388 | expect(chg).to.deep.equal({
|
389 | model: scope.getModel("bar"),
|
390 | previousModel: fallback.getModel("bar"),
|
391 | path: "bar",
|
392 | type: "update",
|
393 | value: "bam",
|
394 | oldValue: "baz"
|
395 | });
|
396 |
|
397 | seen = true;
|
398 | });
|
399 |
|
400 | scope.set("bar", "bam");
|
401 | expect(seen).to.be.ok;
|
402 | });
|
403 |
|
404 | it("doesn't observes changes to fallback when the value is in local model", function() {
|
405 | scope.observe("foo", function(chg) {
|
406 | throw new Error("A change was observed.");
|
407 | });
|
408 |
|
409 | fallback.set("foo", "bam");
|
410 | });
|
411 |
|
412 | it("observes unset on local value, falling back on a secondary value", function() {
|
413 | var seen = false;
|
414 | fallback.set("foo", "bug");
|
415 |
|
416 | scope.observe("foo", function(chg) {
|
417 | expect(chg).to.deep.equal({
|
418 | model: fallback.getModel("foo"),
|
419 | previousModel: scope.getModel("foo"),
|
420 | path: "foo",
|
421 | type: "update",
|
422 | value: "bug",
|
423 | oldValue: "bar"
|
424 | });
|
425 |
|
426 | seen = true;
|
427 | });
|
428 |
|
429 | scope.unset("foo");
|
430 | expect(seen).to.be.ok;
|
431 | });
|
432 | });
|
433 |
|
434 | }); |
\ | No newline at end of file |