Created
August 16, 2018 21:24
-
-
Save emidln/49762cc38761de48be9e257cc4bcff0b to your computer and use it in GitHub Desktop.
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
(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