Skip to content

Instantly share code, notes, and snippets.

@emidln
Created August 16, 2018 21:24
Show Gist options
  • Save emidln/49762cc38761de48be9e257cc4bcff0b to your computer and use it in GitHub Desktop.
Save emidln/49762cc38761de48be9e257cc4bcff0b to your computer and use it in GitHub Desktop.
(ns test-browser-project.core
(:require [clojure.test :refer [with-test testing is]]
[clojure.spec.alpha :as s]))
; Note: Can do this in python or any other language too!
; Browser history:
; Browser should support URL bar, forward and back buttons
; creates the history object - use a class, record, or other structure if you want!
; stores at most `max-count` URLs in the history
(s/def ::max-count integer?)
(s/def ::url string?)
(s/def ::history (s/coll-of ::url))
(s/def ::position-index (s/and integer? (s/or :zero zero?
:positive pos?)))
(s/def ::store-object (s/and (s/keys :req-un [::max-count ::history ::position-index])
#(if (seq (:history %))
(<= 0 (-> % :position-index second) (dec (count (:history %))))
(= 0 (-> % :position-index second)))))
(with-test
(defn create-store
"Initializes a store to maintain up to max-count items in history and the current position"
[max-count]
{:max-count max-count
:history []
:position-index 0})
(testing "establishing store-object shape"
(is (= (create-store 99)
{:max-count 99
:history []
:position-index 0}))))
(s/fdef create-store
:args (s/cat :max-count ::max-count)
:ret ::store-object)
(with-test
(defn store-back-move
"Stores a go back event. Maintains the :position-index pointer in store-object"
[store-object]
(let [new-store (-> store-object
(update-in [:position-index] dec))]
(if (< (:position-index new-store) 0)
(assoc-in new-store [:position-index] 0)
new-store)))
(testing "moves back"
(let [store (create-store 99)]
(is (= (store-back-move store) store))
(is (= (-> (assoc store
:history ["foo.com" "bar.com"]
:position-index 1)
store-back-move
:position-index))
0)
(is (= (-> (assoc store
:history ["foo.com" "bar.com"]
:position-index 0)
store-back-move
:position-index))
0))))
(s/fdef store-back-move
:args (s/cat :store-object ::store-object)
:ret ::store-object)
(with-test
(defn current-url
"Return the current url pointed to by position-index"
[store-object]
(when (seq (:history store-object))
(nth (:history store-object) (:position-index store-object))))
(testing "resolving current url"
(is (nil? (current-url (create-store 2))))
(is (= (current-url {:max-count 2
:history ["foo.com"]
:position-index 0})
"foo.com"))))
(s/fdef current-url
:args (s/cat :store-object ::store-object)
:ret (s/or :empty nil?
:not-empty ::url))
(with-test
(defn store-fwd-move
"Stores a go forward event. Maintains the :position-index pointer in store-object"
[store-object]
(let [new-store (-> store-object
(update-in [:position-index] inc))]
(if (>= (:position-index new-store) (count (:history store-object)))
(assoc-in new-store [:position-index] (dec (count (:history store-object))))
new-store)))
(testing "moves forward"
(let [store (create-store 99)]
(is (= (store-back-move store) store))
(is (= (-> (assoc store
:history ["foo.com" "bar.com"]
:position-index 1)
store-fwd-move
:position-index))
1)
(is (= (-> (assoc store
:history ["foo.com" "bar.com"]
:position-index 0)
store-fwd-move
:position-index))
1))))
(s/fdef store-fwd-move
:args (s/cat :store-object ::store-object)
:ret ::store-object)
; to support “click a link” on the browser
(with-test
(defn store-visit
"Stores a visit to the url. Maintains :history and :position-index inside store-object"
[store-object url]
;; update the history and position for the new url
(let [update-position-index (fn [obj]
(assoc obj :position-index
(if-let [history (seq (:history obj))]
(dec (count history))
;; empty history means the idx is 0
0)))
update-history (fn [obj]
(update-in obj [:history]
#(if (and (seq %)
(not= (:position-index obj)
(dec (count %))))
;; drop items off the end past position-index
(-> (take (inc (:position-index obj)) %)
vec
(conj url))
;; otherwise just add the url
(conj % url))))
new-store (-> store-object
update-history
update-position-index)]
;; ensure we don't violate max-count; also handles max-count being smaller than we knew
(let [new-size (count (:history new-store))
max-count (:max-count store-object)]
(if (> new-size max-count)
(-> new-store
(update-in [:history] #(->> (drop (- new-size max-count) %)
(into [])))
update-position-index)
new-store))))
(let [empty (create-store 3)
one (assoc empty :history ["foo.com"] :position-index 0)
two (assoc empty :history ["foo.com" "bar.com"] :position-index 1)
three (assoc empty :history ["foo.com" "bar.com" "qux.com"] :position-index 2)]
(testing "simple visiting"
(is (= one (store-visit empty "foo.com")))
(is (= two (-> empty (store-visit "foo.com") (store-visit "bar.com")))))
(testing "doesn't violate max-count"
(let [maxxed (store-visit three "example.com")]
(is (= maxxed
{:max-count 3
:history ["bar.com" "qux.com" "example.com"]
:position-index 2})))
(is (= (store-visit (assoc three :max-count 2) "example.com")
{:max-count 2
:history ["qux.com" "example.com"]
:position-index 1})))
(testing "manages position-index appropriately"
(is (= (-> three store-back-move store-back-move (store-visit "example.com"))
{:max-count 3
:history ["foo.com" "example.com"]
:position-index 1})))))
(s/fdef store-visit
:args (s/cat :store-object ::store-object
:url ::url)
:ret ::store-object
:fn (s/and #(= (-> % :args :url)
(-> % :ret :history last))
#(<= (count (-> % :ret :history))
(-> % :ret :max-count))))
(with-test
(defn lookup
"Looks for substring in the history. Returns a sequence of matching urls or nil"
[store-object substring]
(seq (filter #(.contains % substring) (:history store-object))))
(testing "url lookup functionality"
(let [store (-> (create-store 99)
(store-visit "foo.com")
(store-visit "bar.com")
(store-visit "qux.com")
(store-visit "example.com"))]
(is (nil? (lookup store "notfound")))
(is (= (lookup store "com")
["foo.com" "bar.com" "qux.com" "example.com"]))
(is (= (lookup store "foo")
["foo.com"])))))
(s/fdef lookup
:args (s/cat :store-object ::store-object
:substring string?)
:ret (s/or :not-found nil?
:found (s/coll-of ::url)))
(comment
(require '[orchestra.spec.test :as stest])
(stest/instrument)
(clojure.test/test-all-vars *ns*)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment