diff --git a/cypress/e2e/example9001-moves.cy.js b/cypress/e2e/example9001-moves.cy.js index 17e1c4b..53e896e 100644 --- a/cypress/e2e/example9001-moves.cy.js +++ b/cypress/e2e/example9001-moves.cy.js @@ -20,11 +20,15 @@ describe('Example 9001: moves', () => { }) it('test1: e2 - e4', () => { - cy.get('#test1move1Btn').click() + cy.get('#test1StartBtn').click() + .get('#test1Container').should('be.visible') + .get('#test1move1Btn').should('be.visible') + .get('#test1move1Btn').click() .window().then(win => { assert.exists(win.test1move) assert.isTrue(isPromise(win.test1move)) }) + .get('#test1results').should('be.visible') .get('#test1move1finished').should('be.visible') .window().then(win => { assert.exists(win.board1) @@ -34,7 +38,8 @@ describe('Example 9001: moves', () => { }) it('test2: multiple moves', () => { - cy.get('#test2move1Btn').click() + cy.get('#test2StartBtn').click() + .get('#test2move1Btn').click() .window().then(win => { assert.exists(win.test2move1) assert.exists(win.test2move2) @@ -48,7 +53,8 @@ describe('Example 9001: moves', () => { }) it('test3: set position callback', () => { - cy.get('#test3step1Btn').click() + cy.get('#test3StartBtn').click() + .get('#test3step1Btn').click() .window().then(win => { assert.exists(win.test3step1) assert.isTrue(isPromise(win.test3step1)) @@ -61,7 +67,8 @@ describe('Example 9001: moves', () => { }) it('test4: set position Promise', () => { - cy.get('#test4step1Btn').click() + cy.get('#test4StartBtn').click() + .get('#test4step1Btn').click() .get('#test4step1finished').should('not.exist') // wait for animation to finish ... .get('#test4step1finished').should('be.visible') @@ -75,8 +82,10 @@ describe('Example 9001: moves', () => { }) it('test5: orientation', () => { + cy.get('#test5StartBtn').click() + // step1: add Arrows - cy.get('#test5step1Btn').click() + .get('#test5step1Btn').click() .get('#myBoard .arrow-bc3c7').should('have.length', 3) .get('#myBoard .circle-a0266').should('have.length', 0) .get('#myBoard .item-18a5b').should('have.length', 3) diff --git a/cypress/e2e/example9005-config.cy.js b/cypress/e2e/example9005-config.cy.js new file mode 100644 index 0000000..d19cc66 --- /dev/null +++ b/cypress/e2e/example9005-config.cy.js @@ -0,0 +1,37 @@ +describe('Example 9005: config', () => { + beforeEach(() => { + cy.visit('http://localhost:3232/examples/9005.html') + .get('#myBoard .piece-349f8').should('have.length', 32) + .window().then((win) => { + assert.exists(win.board1) + // test that a few of the API methods exist + assert.isFunction(win.board1.position) + assert.isFunction(win.board1.move) + assert.isFunction(win.board1.addArrow) + + // we should be in the start position + assert.equal(win.board1.position('map').size, 32) + assert.equal(win.board1.fen(), "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR") + }) + }) + + it('change config values at runtime', () => { + cy.get('#setConfig1Btn').click() + .window().then(win => { + assert.equal(win.board1.orientation(), 'black') + }) + .get('#setKingPawnEndgameBtn').click() + .get('#myBoard .piece-349f8').should('have.length', 3) + .get('#onChangeTarget').then($div => { + const innerText = $div.text() + assert.equal(innerText, 'change1') + }) + .get('#setConfig2Btn').click() + .get('#setRuyLopezBtn').click() + .get('#myBoard .piece-349f8').should('have.length', 32) + .get('#onChangeTarget').then($div => { + const innerText = $div.text() + assert.equal(innerText, 'change2') + }) + }) +}) diff --git a/examples/3009-circles.example b/examples/3009-circles.example index b8623c8..7bd0b66 100644 --- a/examples/3009-circles.example +++ b/examples/3009-circles.example @@ -38,7 +38,7 @@ const board = Chessboard2('myBoard', 'start') let circle1Id = null let circle2Id = null let circle3Id = null -let circle4Id = null +const circle4Id = null let circle5Id = null attachEvent('addCircle1Btn', 'click', () => { diff --git a/examples/9001-move-test.example b/examples/9001-move-test.example index 5de295d..cb5e517 100644 --- a/examples/9001-move-test.example +++ b/examples/9001-move-test.example @@ -8,41 +8,43 @@ Move Pieces Test This file exists for Cypress testing ===== HTML -
- -
-

test1: e2 - e4

+
+ +
+ + + + + + + +
+ + -
- -
+ -
- -
+ -
- -
+ -
- -
+ -
- ===== JS -const board1 = Chessboard2('myBoard', 'start') +const board1 = Chessboard2('myBoard', 'start') window.board1 = board1 +function byId (id) { + return document.getElementById(id) +} + function appendHtml (id, html) { - const el = document.getElementById(id) + const el = byId(id) el.innerHTML = el.innerHTML + html } +function hideEl (id) { + byId(id).style.display = 'none' +} + +function hideAllSections () { + hideEl('test1Container') + hideEl('test2Container') + hideEl('test3Container') + hideEl('test4Container') + hideEl('test5Container') +} + +function showSection (id) { + byId(id).style.display = '' +} + +attachEvent('test1StartBtn', 'click', () => { hideAllSections(); showSection('test1Container') }) +attachEvent('test2StartBtn', 'click', () => { hideAllSections(); showSection('test2Container') }) +attachEvent('test3StartBtn', 'click', () => { hideAllSections(); showSection('test3Container') }) +attachEvent('test4StartBtn', 'click', () => { hideAllSections(); showSection('test4Container') }) +attachEvent('test5StartBtn', 'click', () => { hideAllSections(); showSection('test5Container') }) + // ----------------------------------------------------------------------------- // Test 1 diff --git a/examples/9004-draggable-test.example b/examples/9004-draggable-test.example index d70c397..358530d 100644 --- a/examples/9004-draggable-test.example +++ b/examples/9004-draggable-test.example @@ -13,6 +13,6 @@ This file exists for testing ===== JS const config = { draggable: true, - position: "start" + position: 'start' } -const board1 = Chessboard2("myBoard", config) +const board1 = Chessboard2('myBoard', config) diff --git a/examples/9005-config-test.example b/examples/9005-config-test.example new file mode 100644 index 0000000..38a4991 --- /dev/null +++ b/examples/9005-config-test.example @@ -0,0 +1,81 @@ +===== id +9005 + +===== Name +Config Test + +===== DescriptionMD +This file exists for testing config settings. + +===== HTML +
+ +
+ + + +
+ +
+ + + + + +

+  
+
+ +===== JS +const board1 = Chessboard2('myBoard', 'start') +window.board1 = board1 + +const config2 = { + onChange: onChange2 +} + +function byId (id) { + return document.getElementById(id) +} + +function appendHtml (id, html) { + const el = byId(id) + el.innerHTML = el.innerHTML + html +} + +// ----------------------------------------------------------------------------- +// Test 1 + +function onChange1 () { + byId('onChangeTarget').innerHTML = 'change1' +} + +function onChange2 () { + byId('onChangeTarget').innerHTML = 'change2' +} + +attachEvent('setConfig1Btn', 'click', () => { + // should warn that "banana" is not a valid value for "orientation" + board1.config('orientation', 'banana') + // should warn-log that "foo" is not a valid config property + board1.config('foo', 'bar') + + board1.config('orientation', 'black') + board1.config('onChange', onChange1) +}) + +attachEvent('setConfig2Btn', 'click', () => { + board1.setConfig(config2) +}) + +attachEvent('setStartBtn', 'click', () => { + board1.start() +}) + +attachEvent('setRuyLopezBtn', 'click', () => { + board1.position('r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R') +}) + +attachEvent('setKingPawnEndgameBtn', 'click', () => { + board1.position('8/8/8/3pk3/8/4K3/8/8') +}) diff --git a/scripts/website.js b/scripts/website.js index 5503acb..62521a8 100755 --- a/scripts/website.js +++ b/scripts/website.js @@ -19,7 +19,7 @@ const cmReader = new commonmark.Parser() const cmWriter = new commonmark.HtmlRenderer() // toggle development version -const useLocalDevFiles = false +const useLocalDevFiles = true const jsCDNScript = '' // const esmCDNScript = '' const cssCDNLink = '' diff --git a/src-cljs/com/oakmac/chessboard2/api.cljs b/src-cljs/com/oakmac/chessboard2/api.cljs index 37fc632..a329449 100644 --- a/src-cljs/com/oakmac/chessboard2/api.cljs +++ b/src-cljs/com/oakmac/chessboard2/api.cljs @@ -1,20 +1,33 @@ (ns com.oakmac.chessboard2.api "Functions that represent the CLJS API for Chessboard2" (:require + [clojure.string :as str] [com.oakmac.chessboard2.animations :refer [animation->dom-op calculate-animations]] - [com.oakmac.chessboard2.constants :refer [animate-speed-strings->times]] + [com.oakmac.chessboard2.config :as config] + [com.oakmac.chessboard2.constants :refer [animate-speed-strings->times start-position]] [com.oakmac.chessboard2.dom-ops :as dom-ops] [com.oakmac.chessboard2.feature-flags :as flags] [com.oakmac.chessboard2.html :as html] [com.oakmac.chessboard2.util.arrows :as arrow-util] [com.oakmac.chessboard2.util.dom :as dom-util :refer [get-element set-inner-html! set-style-prop!]] + [com.oakmac.chessboard2.util.fen :refer [fen->position valid-fen?]] [com.oakmac.chessboard2.util.ids :refer [random-id]] [com.oakmac.chessboard2.util.logging :refer [warn-log]] [com.oakmac.chessboard2.util.moves :refer [apply-move-to-position]] - [com.oakmac.chessboard2.util.predicates :refer [arrow-item? valid-square? valid-position?]] + [com.oakmac.chessboard2.util.predicates :refer [arrow-item? fen-string? start-string? valid-square? valid-position?]] [com.oakmac.chessboard2.util.squares :refer [square->dimensions]] [goog.object :as gobj])) +;; TODO: move this to a util namespace +; (defn coerce-to-position-map +; "Does it's best to coerce p into a position map if possible" +; [p] +; (cond +; (start-string? p) start-position +; (valid-fen? p) (fen->position p) +; (valid-position? p) p +; :else nil)) + (defn get-items-by-type "Returns a map of Items on the board" [board-state type-str] @@ -376,3 +389,28 @@ (reset! board-state nil)) ;; return null nil) + +(defn update-config! + "Update the board config with new values." + [board-state new-config] + (let [;; do not allow them to update the position via this method + cfg2 (dissoc new-config :position) + validated-config (reduce + (fn [cfg3 [prop val]] + (if-not (contains? config/valid-config-keys prop) + ;; Google Closure adds these keys to Objects for some reason ¯\_(ツ)_/¯ + ;; do not log and confuse the end user + (do (when-not (str/starts-with? (name prop) "closure_uid") + (warn-log "Invalid config property:" (name prop))) + cfg3) + (let [validation-fn (get-in config/config-props [prop :valid-fn]) + valid-value? (validation-fn val)] + (if-not valid-value? + (do (warn-log (str "Invalid value for config property \"" (name prop) "\": " + val)) + cfg3) + (assoc cfg3 prop val))))) + {} + cfg2)] + (swap! board-state merge validated-config) + nil)) diff --git a/src-cljs/com/oakmac/chessboard2/config.cljs b/src-cljs/com/oakmac/chessboard2/config.cljs index 7f199b2..772b055 100644 --- a/src-cljs/com/oakmac/chessboard2/config.cljs +++ b/src-cljs/com/oakmac/chessboard2/config.cljs @@ -3,6 +3,7 @@ [com.oakmac.chessboard2.constants :refer [animate-speed-strings animate-speed-strings->times start-position]] [com.oakmac.chessboard2.util.data-transforms :refer [clj->js-map js-map->clj]] [com.oakmac.chessboard2.util.fen :refer [fen->position position->fen valid-fen?]] + [com.oakmac.chessboard2.util.logging :refer [warn-log]] [com.oakmac.chessboard2.util.predicates :refer [fen-string? map-string? start-string? @@ -86,6 +87,11 @@ (def valid-config-keys (set (keys config-props))) +(def valid-config-strings + (->> valid-config-keys + (map name) + set)) + ;; TODO: Good candidate for unit tests (defn merge-config [their-config] @@ -98,3 +104,8 @@ (assoc new-config config-key default-val)))) {} config-props)) + +(defn state->config + "Given the board-state, return the public config object" + [board-state] + (select-keys board-state valid-config-keys)) diff --git a/src-cljs/com/oakmac/chessboard2/core.cljs b/src-cljs/com/oakmac/chessboard2/core.cljs index c249726..d13124f 100644 --- a/src-cljs/com/oakmac/chessboard2/core.cljs +++ b/src-cljs/com/oakmac/chessboard2/core.cljs @@ -14,6 +14,7 @@ [com.oakmac.chessboard2.util.dom :as dom-util :refer [add-class! append-html! remove-class! remove-element!]] [com.oakmac.chessboard2.util.fen :refer [fen->position valid-fen?]] [com.oakmac.chessboard2.util.ids :refer [random-id]] + [com.oakmac.chessboard2.util.lang :refer [atom?]] [com.oakmac.chessboard2.util.logging :refer [error-log warn-log]] [com.oakmac.chessboard2.util.moves :refer [move->map]] [com.oakmac.chessboard2.util.pieces :refer [random-piece-id]] @@ -703,7 +704,6 @@ squares-el (dom-util/get-element squares-selector)] (remove-class! squares-el css/orientation-black) (add-class! squares-el css/orientation-white) - (swap! board assoc :orientation "white") (draw-items-instant! board))) (defn set-black-orientation! @@ -712,7 +712,6 @@ squares-el (dom-util/get-element squares-selector)] (remove-class! squares-el css/orientation-white) (add-class! squares-el css/orientation-black) - (swap! board assoc :orientation "black") (draw-items-instant! board))) (defn orientation @@ -721,15 +720,15 @@ ([board arg] (let [lc-arg (safe-lower-case arg)] (cond - (= lc-arg "white") (do (set-white-orientation! board) + (= lc-arg "white") (do (swap! board assoc :orientation "white") "white") - (= lc-arg "black") (do (set-black-orientation! board) + (= lc-arg "black") (do (swap! board assoc :orientation "black") "black") (= lc-arg "flip") (do (swap! board update :orientation toggle-orientation) (let [new-orientation (:orientation @board)] (if (= new-orientation "white") - (set-white-orientation! board) - (set-black-orientation! board)) + (swap! board assoc :orientation "white") + (swap! board assoc :orientation "black")) new-orientation)) :else (:orientation @board))))) @@ -836,9 +835,13 @@ ; (def default-animate-speed-ms 2500) (defn board-state-change - [_key _atom old-state new-state] + [_key board-atom old-state new-state] (when new-state - ;; FIXME: board orientation + ;; board orientation + (when-not (= (:orientation old-state) (:orientation new-state)) + (if (= "white" (:orientation new-state)) + (set-white-orientation! board-atom) + (set-black-orientation! board-atom))) ;; FIXME: coordinate config change ;; show / hide coordinates (when-not (= (:show-coords? old-state) (:show-coords? new-state)) @@ -912,11 +915,9 @@ "getCircles" (partial js-get-circles board-state) "removeCircle" (partial js-remove-circle board-state) - ;; FIXME: implement these - ; https://github.com/oakmac/chessboard2/issues/7 - ; "config" #() - ; "getConfig" #() - ; "setConfig" #() + "config" (partial js-api/config board-state) + "getConfig" (partial js-api/get-config board-state) + "setConfig" (partial js-api/set-config board-state) ;; FIXME: allow adding custom items ;; https://github.com/oakmac/chessboard2/issues/9 diff --git a/src-cljs/com/oakmac/chessboard2/js_api.cljs b/src-cljs/com/oakmac/chessboard2/js_api.cljs index 201662f..39e342a 100644 --- a/src-cljs/com/oakmac/chessboard2/js_api.cljs +++ b/src-cljs/com/oakmac/chessboard2/js_api.cljs @@ -273,3 +273,39 @@ (if (= 1 (count moves)) (clj->js (first moves)) (clj->js moves)))) + +(defn get-config + "Returns the current board config as a JS Object" + [board-state] + (clj->js (config/state->config @board-state))) + +;; TODO: would be nice to unit test the warning logs here via mocks +(defn set-config + "Set the config." + [board-state arg1 arg2] + (cond + ;; set a single config value + (contains? config/valid-config-strings arg1) + (do (api/update-config! board-state {(keyword arg1) arg2}) + (get-config board-state)) + + ;; set multiple values via object + (goog/isObject arg1) + (do (api/update-config! board-state (js->clj arg1 :keywordize-keys true)) + (get-config board-state)) + + :else (warn-log "Invalid args passed to setConfig():" arg1 arg2))) + +(defn config + "Get or Set the board config" + [board-state] + (let [js-args (array)] + (copy-arguments js-args) + (.shift js-args) + (let [arg1 (aget js-args 0) + arg2 (aget js-args 1)] + (cond + (not arg1) (get-config board-state) + (string? arg1) (set-config board-state arg1 arg2) + (goog/isObject arg1) (set-config board-state arg1 nil) + :else (warn-log "Invalid args passed to config():" arg1 arg2))))) diff --git a/src-cljs/com/oakmac/chessboard2/util/lang.cljs b/src-cljs/com/oakmac/chessboard2/util/lang.cljs new file mode 100644 index 0000000..180098a --- /dev/null +++ b/src-cljs/com/oakmac/chessboard2/util/lang.cljs @@ -0,0 +1,7 @@ +(ns com.oakmac.chessboard2.util.lang) + +;; TODO: candidate for removal +;; TODO: could use unit tests +(defn atom? + [a] + (satisfies? IAtom a))