├── .gitignore
├── LICENSE
├── README.md
├── project.clj
├── quantum-entanglement1.png
├── src
└── entanglement
│ └── core.cljs
└── test
├── runtests.cljs
├── test.html
└── testcursor.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
205 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Entanglement
2 | ==============
3 |
4 | > "Spooky action at a distance" -Einstein
5 |
6 | What if an atom could take its values from another atom?
7 |
8 |
10 |
11 | Same data, different representation.
12 |
13 |
14 | ```clj
15 | ;; Those two atoms **SHARE THE SAME DATA**
16 | atom-a
17 | #
18 |
19 | atom-b
20 | #
21 |
22 | ;; Being the same data, you can reset one...
23 |
24 | (reset! atom-b ["Bob" 35])
25 | ["Bob" 35]
26 |
27 | ;; and the other will reflect this change!
28 |
29 | atom-a
30 | #
31 | ```
32 |
33 | You can now have an abstaction layer between your state (atom) and
34 | your program.
35 |
36 |
37 | Usage
38 | -----
39 |
40 | Add this to your project dependencies:
41 |
42 | [](http://clojars.org/org.clojars.frozenlock/entanglement)
43 |
44 |
45 | In your namespace declaration: `(:require [entanglement.core :refer [entangle]])`.
46 |
47 | To re-create `atom-a` and `atom-b` from the example above:
48 |
49 | ```clj
50 | ;; Let's create the reference atom
51 | (def atom-a (atom {:first-name "Mike" :last-name "Moran" :age 22 :sex "M"}))
52 |
53 | ;; And now we make an atom taking values from the first atom:
54 | (def atom-b (entangle atom-a
55 | ;; getter function
56 | (fn [s-a]
57 | [(:first-name s-a) (:age s-a)])
58 | ;; optional setter function
59 | (fn [s-a new-value]
60 | (assoc s-a
61 | :first-name (first new-value)
62 | :age (last new-value)))
63 | ;;optional validator
64 | :validator
65 | (fn [new-value]
66 | (assert (-> new-value first string?) "First value should be a string")
67 | (assert (-> new-value last number?) "Second value should be a number"))
68 | ;;optional identifier to avoid duplicate watches (see docstring)
69 | :identifier
70 | :my-entangled-atom))
71 | ```
72 |
73 | Rationale
74 | -------
75 |
76 | An atom is a nice state abstraction. You have this 'thing' that holds
77 | data and with which you must thread carefully. It has a limited set of
78 | functions to play with it (`deref`,`reset!`, `swap!`,
79 | `add-watch`/`remove-watch`). Simple, yet functional.
80 |
81 | The problem is that the atom does not necessarily match the simplest
82 | code architecture.
83 |
84 | If you build your code to reflect how the data is stored in the atom,
85 | you are adding complexity. The more detached the atom structure is
86 | from the code logic, the more you have to juggle the data around. It
87 | also means that your code becomes intertwined with this particular
88 | atom.
89 |
90 | Before you know it, your entire code base is dependant on one or more
91 | atoms having a particular structure. So long for reusable functions.
92 |
93 | `Entanglement` proproses to create atoms from other atoms and linking
94 | the data together. It lets you build an 'interface' that presents
95 | the data like you want it. Build your code in the simplest way
96 | possible, assuming the atom will match what you want.
97 |
98 | ```clj
99 | ;; what was...
100 | (swap! my-atom update-in [:some :path :that :might :be :quite :deep] my-fn)
101 |
102 | ;; ...can now become
103 |
104 | (swap! my-atom my-fn)
105 | ```
106 |
107 | "Wait, this looks a lot like cursors?"
108 |
109 | Right, because it is! In fact, cursors are a subset of entangled atoms.
110 | Here is an implement of cursors with `entanglement`:
111 | ```clj
112 | (defn cursor
113 | "Create a cursor. Behaves like a normal atom for the value at the
114 | specified path."
115 | [a path]
116 | (if-not (seq path) ;; if the path is emtpy, just return the atom...
117 | a
118 | (entangle a
119 | #(get-in % path)
120 | #(assoc-in %1 path %2)
121 | :identifier [::cursor a path])))
122 | ```
123 |
124 | (Cursors are such a common case that we already provide the `cursor`
125 | function in `entanglement.core`.)
126 |
127 | Cursors, lenses, wraps... they are all symptomatic of the need to
128 | detach atom data structure from the code.
129 |
130 | Every atom made with `entanglement` is 100% opaque. From the functions
131 | point of view, it's just like any other atom.
132 |
133 |
134 | Warning
135 | -------
136 |
137 | Watches are not added to entangled atoms, but rather to the source
138 | atoms. This means that one should be careful when adding watches
139 | because *they won't be automatically GCed* even if the entangled atoms
140 | are.
141 |
142 |
143 | License
144 | -------
145 |
146 | Copyright (c) 2015 Frozenlock
147 |
148 | Distributed under the Eclipse Public License, the same as Clojure.
149 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject org.clojars.frozenlock/entanglement "0.0.4"
2 | :description "Spooky action at a distance (between atoms)"
3 | :url "https://github.com/Frozenlock/entanglement"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.7.0-alpha4"]
7 | [org.clojure/clojurescript "0.0-2356"]]
8 | :plugins [[lein-cljsbuild "1.0.3"]
9 | [com.cemerick/clojurescript.test "0.3.3"]]
10 | :profiles {:test {:cljsbuild
11 | {:builds
12 | {:client {:source-paths ^:replace
13 | ["test" "src"]}}}}}
14 | :source-paths ["src"]
15 | :cljsbuild
16 | {:builds
17 | {:client {:source-paths ["src"]
18 | :compiler
19 | {
20 | :output-dir "target/client"
21 | :output-to "target/cljs-client.js"
22 | :pretty-print true}}}})
23 |
--------------------------------------------------------------------------------
/quantum-entanglement1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Frozenlock/entanglement/ab9bcc1904a97743d58936d01129e6822ac1cda8/quantum-entanglement1.png
--------------------------------------------------------------------------------
/src/entanglement/core.cljs:
--------------------------------------------------------------------------------
1 | (ns entanglement.core)
2 |
3 | (deftype Entangled [source-atom meta validator watches
4 | getter setter derefer identifier]
5 | Object
6 | (equiv [this other]
7 | (-equiv this other))
8 |
9 | IAtom
10 |
11 | IEquiv
12 | (-equiv [this other]
13 | (and (instance? Entangled other)
14 | (or (= identifier (.-identifier other))
15 | (= identical? this other))
16 | (= source-atom (.-source-atom other))))
17 |
18 | IDeref
19 | (-deref [this]
20 | (let [derefer (or derefer (fn [_ a g]
21 | (g (-deref a))))]
22 | (derefer this source-atom getter)))
23 |
24 | IMeta
25 | (-meta [_] meta)
26 |
27 | IWithMeta
28 | (-with-meta [o meta]
29 | (Entangled. source-atom meta validator watches
30 | getter setter derefer identifier))
31 |
32 | ;; Every watch is added to the source atom. This way, every time a
33 | ;; source atom is modified, it will ripple thought all the entangled
34 | ;; children.
35 |
36 | ;; Might cause memory leaks? (If we add-watch an entangled atom, it is
37 | ;; now referenced in the source atom, meaning it shouldn't be
38 | ;; GCed...)
39 | IWatchable
40 | (-add-watch [this key f]
41 | (add-watch source-atom [(or identifier this) key]
42 | (fn [_ _ oldval newval]
43 | (when-not (= oldval newval) ;; the getter fn can be
44 | ;; expensive, better to
45 | ;; avoid it if we can.
46 | (let [old (getter oldval)
47 | new (getter newval)]
48 | (when-not (= old new)
49 | (f key this old new))))))
50 | this)
51 |
52 | (-remove-watch [this key]
53 | (remove-watch source-atom [(or identifier this) key])
54 | this)
55 |
56 |
57 | ;; possibility of creating a read-only atom by omitting a setter
58 | ;; function
59 |
60 | IReset
61 | (-reset! [a new-value]
62 | (if setter
63 | (let [validate (.-validator a)]
64 | (when-not (nil? validate)
65 | (assert (validate new-value) "Validator rejected reference state"))
66 | (do (swap! source-atom setter new-value)
67 | new-value))
68 | (throw (js/Error. "Read-only: no setter provided for this atom."))))
69 |
70 | ISwap
71 | (-swap! [a f]
72 | (reset! a (f (-deref a))))
73 | (-swap! [a f x]
74 | (reset! a (f (-deref a) x)))
75 | (-swap! [a f x y]
76 | (reset! a (f (-deref a) x y)))
77 | (-swap! [a f x y more]
78 | (reset! a (apply f (-deref a) x y more)))
79 |
80 | IPrintWithWriter
81 | (-pr-writer [a writer opts]
82 | (-write writer "#"))
85 |
86 | IHash
87 | (-hash [this] (goog/getUid this)))
88 |
89 |
90 |
91 | ;;;;; Main API
92 |
93 | (defn entangle
94 | "Return an atom which applies custom getter and setter to the
95 | source-atom for every lookup/update/watches.
96 |
97 | getter: (fn [derefed-source-atom] ...)
98 | setter [optional]: (fn [derefed-source-atom new-value]...)
99 |
100 | derefer [optional]: (fn [this(new-atom) source-atom getter] ....)
101 |
102 | identifier [optional]: :some-id -- more info below
103 |
104 | When creating delicate entanglement (when the datastructure between
105 | the source atom and the new atom are quite different), it is
106 | suggested to provide a validator function.
107 |
108 | The validator and meta arguments act the same way as for normal
109 | atoms.
110 |
111 | A 'read-only' atom can be created simply by omitting or passing nil
112 | as the setter argument. Any attempt to modify directly the returned
113 | atom will result in an error.
114 |
115 | Because we can't test for equality between functions (getter and
116 | setter), basic entangled atoms can't test for equality. This is
117 | especially important when adding watches, as they need to test for
118 | equality for potential duplicates. To avoid this problem, it's
119 | possible to provide a 'identifier' field which contains the object
120 | on which the equality should be tested."
121 | ([source-atom getter] (entangle source-atom getter nil))
122 | ([source-atom getter setter & {:keys [meta validator derefer identifier]}]
123 | (assert (satisfies? IAtom source-atom) "Only atoms can be entangled.")
124 | (Entangled. source-atom meta validator nil getter setter derefer identifier)))
125 |
126 |
127 | ;;; Simple cursor implementation using entanglement
128 |
129 | (defn cursor
130 | "Create a cursor. Behaves like a normal atom for the value at the
131 | specified path."
132 | [a path]
133 | (if-not (seq path) ;; if the path is emtpy, just return the atom...
134 | a
135 | (entangle a
136 | #(get-in % path)
137 | #(assoc-in %1 path %2)
138 | :identifier [::cursor a path]))) ;; <- identifier to test for equality
139 |
140 |
--------------------------------------------------------------------------------
/test/runtests.cljs:
--------------------------------------------------------------------------------
1 | (ns runtests
2 | (:require-macros [cemerick.cljs.test
3 | :refer (is deftest with-test run-tests testing)])
4 | (:require [testcursor]
5 | [cemerick.cljs.test :as t]))
6 |
7 | (enable-console-print!)
8 |
9 | (def test-results (atom nil))
10 |
11 | (defn ^:export run-all-tests []
12 | (println "-----------------------------------------")
13 | (try
14 | (reset! test-results (t/run-all-tests))
15 | (catch js/Object e
16 | (do
17 | (println "Testrun failed\n" e "\n" (.-stack e))
18 | (reset! test-results {:error e}))))
19 | (println "-----------------------------------------"))
20 |
21 |
--------------------------------------------------------------------------------
/test/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Testing Entanglement
5 |
8 |
9 |
10 | Check the console
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/testcursor.cljs:
--------------------------------------------------------------------------------
1 | (ns testcursor
2 | (:require-macros [cemerick.cljs.test
3 | :refer (is deftest with-test run-tests testing)])
4 | (:require [cemerick.cljs.test :as t]
5 | [entanglement.core :as ent]))
6 |
7 | ;; Here we test if the cursors act as a normal atom
8 |
9 | (deftest values
10 | (let [test-atom (atom {:a {:b {:c {:d 1}}}})
11 | test-cursor (ent/cursor test-atom [:a :b :c :d])
12 | test-cursor2 (ent/cursor test-atom [])] ;; nasty edge case
13 |
14 | (is (= cljs.core/Atom (type test-cursor2)))
15 |
16 | ;; get the initial values
17 | (is (= (get-in @test-atom [:a :b :c :d])
18 | @test-cursor))
19 |
20 | (is (= (get-in @test-atom [])
21 | @test-cursor2))
22 |
23 | ;; now we update the cursor with a reset
24 | (reset! test-cursor 2)
25 | (is (= @test-cursor 2))
26 | (is (= (get-in @test-atom [:a :b :c :d]) 2))
27 |
28 | (reset! test-cursor2 3)
29 | (is (= @test-cursor2 3))
30 | (is (= @test-atom 3))
31 | (reset! test-atom {:a {:b {:c {:d 1}}}}) ;; restore test-atom
32 |
33 | ;; swap
34 | (reset! test-cursor {}) ;; empty map
35 | (swap! test-cursor assoc :z 3)
36 | (is (= @test-cursor {:z 3}))
37 | (is (= (get-in @test-atom [:a :b :c :d])
38 | {:z 3}))
39 |
40 | (reset! test-cursor2 {}) ;; empty map
41 | (swap! test-cursor2 assoc :z 3)
42 | (is (= @test-cursor2 {:z 3}))
43 | (is (= (get-in @test-atom [])
44 | {:z 3}))))
45 |
46 |
47 | (deftest atom-behaviors
48 | (let [test-atom (atom {:a {:b {:c {:d 1}}}})
49 | test-cursor (ent/cursor test-atom [:a :b :c :d])
50 | witness (atom nil)]
51 | ;; per the description, reset! should return the new values
52 | (is (= {}
53 | (reset! test-cursor {})))
54 |
55 | ;; per the description, swap! should return the new values
56 | (is (= {:z [1 2 3]}
57 | (swap! test-cursor assoc :z [1 2 3])))
58 |
59 | ;; watches should behave like with a normal atom
60 | (reset! test-cursor "old")
61 | (add-watch test-cursor :w #(reset! witness {:key %1 :ref %2 :old %3 :new %4}))
62 | (reset! test-cursor "new") ;; this should trigger the watch function
63 | (is (= (:key @witness) :w))
64 | (is (= (:ref @witness) test-cursor))
65 | (is (= (:old @witness) "old"))
66 | (is (= (:new @witness) "new"))
67 | ;; can we remove the watch?
68 | (remove-watch test-cursor :w)
69 | (reset! test-cursor "removed")
70 | (is (= (:new @witness) "new")) ;; shouldn't have changed
71 | ))
72 |
--------------------------------------------------------------------------------