docs: add agent rules for dev server management and browser verification
- Section 6: Never start dev servers from the agent (prevents orphan processes and broken tmux panes) - Section 7: Check tmux panes for compile errors (renumbered) - Section 8: Verify compiled output in browser before committing (catches squint's silent empty-file failures)
This commit is contained in:
37
AGENTS.md
37
AGENTS.md
@@ -192,7 +192,18 @@ bb build-theme # Regenerate CSS with new component styles
|
|||||||
bb test # All tests pass
|
bb test # All tests pass
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Check running dev servers before committing — CRITICAL
|
### 6. Never start dev servers from the agent — CRITICAL
|
||||||
|
|
||||||
|
**Do not run `bb dev`, `bb dev-hiccup`, `bb dev-replicant`, `bb dev-squint`, or any long-running server process from the agent.** The user manages dev servers in a separate tmux session (`ui-dev`). Starting servers from the agent blocks the session, spawns orphan processes, and can break existing tmux panes.
|
||||||
|
|
||||||
|
The agent may only:
|
||||||
|
- Run short commands: `bb test`, `bb build-theme`, `curl`, `wc -l`, `grep`
|
||||||
|
- Inspect tmux panes via `tmux capture-pane`
|
||||||
|
- Touch files to trigger recompilation: `touch src/ui/<module>.cljc`
|
||||||
|
|
||||||
|
If a dev server needs restarting, **tell the user** — don't do it yourself.
|
||||||
|
|
||||||
|
### 7. Check running dev servers before committing — CRITICAL
|
||||||
|
|
||||||
A tmux session `ui-dev` runs all three dev servers (`bb dev-all`). **Always check every pane for compile errors before committing:**
|
A tmux session `ui-dev` runs all three dev servers (`bb dev-all`). **Always check every pane for compile errors before committing:**
|
||||||
|
|
||||||
@@ -212,6 +223,30 @@ Look for:
|
|||||||
|
|
||||||
Do **not** commit if any pane shows errors. Fix them first.
|
Do **not** commit if any pane shows errors. Fix them first.
|
||||||
|
|
||||||
|
### 8. Verify pages load in browser before committing — CRITICAL
|
||||||
|
|
||||||
|
Terminal compile checks alone are **not enough**. Squint can produce empty `.mjs` files silently (no errors in the terminal). Use the `fetch` tool to verify each dev server actually serves a working page:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Check hiccup (server-rendered — look for actual HTML content)
|
||||||
|
curl -s http://localhost:4003 | grep -c 'sidebar-layout'
|
||||||
|
|
||||||
|
# Check squint compiled output is not empty (must be >1 line)
|
||||||
|
wc -l dev/squint/.compiled/dev/squint.mjs
|
||||||
|
wc -l dev/squint/.compiled/ui/*.mjs | sort -n | head -5
|
||||||
|
# Any file with only 1 line is broken — recompile it:
|
||||||
|
# touch src/ui/<module>.cljc
|
||||||
|
|
||||||
|
# Check replicant compiled JS exists
|
||||||
|
ls -la dev/replicant/public/js/cljs-runtime/ui.sidebar.js
|
||||||
|
```
|
||||||
|
|
||||||
|
If any compiled file is suspiciously small (1 line = just the import), touch the source file to trigger rewatch, or manually compile:
|
||||||
|
```sh
|
||||||
|
cd dev/squint && npx squint compile ../../src/ui/<module>.cljc
|
||||||
|
cd dev/squint && npx squint compile src/dev/squint.cljs
|
||||||
|
```
|
||||||
|
|
||||||
## Theme System
|
## Theme System
|
||||||
|
|
||||||
### Token naming
|
### Token naming
|
||||||
|
|||||||
@@ -356,9 +356,12 @@
|
|||||||
(sidebar/sidebar-menu-item {:href "#"} "redirect")))))
|
(sidebar/sidebar-menu-item {:href "#"} "redirect")))))
|
||||||
(sidebar/sidebar-footer {}
|
(sidebar/sidebar-footer {}
|
||||||
(sidebar/sidebar-user {:user-name "Alice Johnson" :email "alice@example.com"})))
|
(sidebar/sidebar-user {:user-name "Alice Johnson" :email "alice@example.com"})))
|
||||||
|
(sidebar/sidebar-overlay {})
|
||||||
(sidebar/sidebar-layout-main {}
|
(sidebar/sidebar-layout-main {}
|
||||||
[:div {:style "padding: 2rem;"}
|
[:div {:style "padding: 2rem;"}
|
||||||
[:h3 {:style "margin: 0 0 1rem; color: var(--fg-0);"} "Dashboard"]
|
[:div {:style "display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"}
|
||||||
|
(sidebar/sidebar-mobile-toggle {})
|
||||||
|
[:h3 {:style "margin: 0; color: var(--fg-0);"} "Dashboard"]]
|
||||||
[:div {:style "display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"}
|
[:div {:style "display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;"}
|
||||||
[:div {:style "aspect-ratio: 16/9; background: var(--bg-1); border-radius: var(--radius-lg); border: var(--border-0);"}]
|
[:div {:style "aspect-ratio: 16/9; background: var(--bg-1); border-radius: var(--radius-lg); border: var(--border-0);"}]
|
||||||
[:div {:style "aspect-ratio: 16/9; background: var(--bg-1); border-radius: var(--radius-lg); border: var(--border-0);"}]
|
[:div {:style "aspect-ratio: 16/9; background: var(--bg-1); border-radius: var(--radius-lg); border: var(--border-0);"}]
|
||||||
@@ -435,8 +438,11 @@
|
|||||||
[:body
|
[:body
|
||||||
(sidebar/sidebar-layout {}
|
(sidebar/sidebar-layout {}
|
||||||
(app-sidebar active-page port)
|
(app-sidebar active-page port)
|
||||||
|
(sidebar/sidebar-overlay {})
|
||||||
(sidebar/sidebar-layout-main {}
|
(sidebar/sidebar-layout-main {}
|
||||||
[:div {:style "padding: 2rem; max-width: 960px;"}
|
[:div {:style "padding: 2rem; max-width: 960px;"}
|
||||||
|
[:div {:style "display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem;"}
|
||||||
|
(sidebar/sidebar-mobile-toggle {})]
|
||||||
(case active-page
|
(case active-page
|
||||||
:components (components-page)
|
:components (components-page)
|
||||||
:icons (icons-page)
|
:icons (icons-page)
|
||||||
|
|||||||
@@ -344,6 +344,14 @@
|
|||||||
(js/requestAnimationFrame
|
(js/requestAnimationFrame
|
||||||
#(js-delete (.-dataset el) "noTransitions"))))
|
#(js-delete (.-dataset el) "noTransitions"))))
|
||||||
|
|
||||||
|
(defn toggle-sidebar! [_e]
|
||||||
|
(when-let [layout (.querySelector js/document ".sidebar-layout")]
|
||||||
|
(.toggleAttribute layout "data-sidebar-open")))
|
||||||
|
|
||||||
|
(defn close-sidebar! [_e]
|
||||||
|
(when-let [layout (.querySelector js/document ".sidebar-layout")]
|
||||||
|
(.removeAttribute layout "data-sidebar-open")))
|
||||||
|
|
||||||
;; ── App Shell ───────────────────────────────────────────────────────
|
;; ── App Shell ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
(defn own-port []
|
(defn own-port []
|
||||||
@@ -391,8 +399,11 @@
|
|||||||
(let [active-page @!page]
|
(let [active-page @!page]
|
||||||
(sidebar/sidebar-layout {}
|
(sidebar/sidebar-layout {}
|
||||||
(app-sidebar active-page)
|
(app-sidebar active-page)
|
||||||
|
(sidebar/sidebar-overlay {:on-click close-sidebar!})
|
||||||
(sidebar/sidebar-layout-main {}
|
(sidebar/sidebar-layout-main {}
|
||||||
[:div {:style {:padding "2rem" :max-width "960px"}}
|
[:div {:style {:padding "2rem" :max-width "960px"}}
|
||||||
|
[:div {:style {:display "flex" :align-items "center" :gap "0.75rem" :margin-bottom "1rem"}}
|
||||||
|
(sidebar/sidebar-mobile-toggle {:on-click toggle-sidebar!})]
|
||||||
(case active-page
|
(case active-page
|
||||||
:components (components-page)
|
:components (components-page)
|
||||||
:icons (icons-page)
|
:icons (icons-page)
|
||||||
|
|||||||
@@ -33,6 +33,14 @@
|
|||||||
(js/requestAnimationFrame
|
(js/requestAnimationFrame
|
||||||
(fn [] (.removeAttribute el "data-no-transitions")))))
|
(fn [] (.removeAttribute el "data-no-transitions")))))
|
||||||
|
|
||||||
|
(defn toggle-sidebar! [_e]
|
||||||
|
(when-let [layout (.querySelector js/document ".sidebar-layout")]
|
||||||
|
(.toggleAttribute layout "data-sidebar-open")))
|
||||||
|
|
||||||
|
(defn close-sidebar! [_e]
|
||||||
|
(when-let [layout (.querySelector js/document ".sidebar-layout")]
|
||||||
|
(.removeAttribute layout "data-sidebar-open")))
|
||||||
|
|
||||||
(defn section [title & children]
|
(defn section [title & children]
|
||||||
[:section {:style {"margin-bottom" "2.5rem"}}
|
[:section {:style {"margin-bottom" "2.5rem"}}
|
||||||
[:h3 {:style {"color" "var(--fg-1)" "margin-bottom" "1rem"
|
[:h3 {:style {"color" "var(--fg-1)" "margin-bottom" "1rem"
|
||||||
@@ -404,8 +412,11 @@
|
|||||||
(let [active-page @!page]
|
(let [active-page @!page]
|
||||||
(sidebar/sidebar-layout {}
|
(sidebar/sidebar-layout {}
|
||||||
(app-sidebar active-page)
|
(app-sidebar active-page)
|
||||||
|
(sidebar/sidebar-overlay {:on-click close-sidebar!})
|
||||||
(sidebar/sidebar-layout-main {}
|
(sidebar/sidebar-layout-main {}
|
||||||
[:div {:style {"padding" "2rem" "max-width" "960px"}}
|
[:div {:style {"padding" "2rem" "max-width" "960px"}}
|
||||||
|
[:div {:style {"display" "flex" "align-items" "center" "gap" "0.75rem" "margin-bottom" "1rem"}}
|
||||||
|
(sidebar/sidebar-mobile-toggle {:on-click toggle-sidebar!})]
|
||||||
(case active-page
|
(case active-page
|
||||||
"components" (components-page)
|
"components" (components-page)
|
||||||
"icons" (icons-page)
|
"icons" (icons-page)
|
||||||
|
|||||||
@@ -369,6 +369,89 @@
|
|||||||
[:span {:class "sidebar-collapsible-chevron" :aria-hidden "true"}]]
|
[:span {:class "sidebar-collapsible-chevron" :aria-hidden "true"}]]
|
||||||
(into [:div {:class "sidebar-collapsible-content"}] children)])))
|
(into [:div {:class "sidebar-collapsible-content"}] children)])))
|
||||||
|
|
||||||
|
;; ── Sidebar Mobile Toggle ────────────────────────────────────────────
|
||||||
|
|
||||||
|
(defn sidebar-mobile-toggle-class-list [_opts] ["sidebar-mobile-toggle"])
|
||||||
|
(defn sidebar-mobile-toggle-classes [opts] (str/join " " (sidebar-mobile-toggle-class-list opts)))
|
||||||
|
|
||||||
|
(defn sidebar-mobile-toggle
|
||||||
|
"Render a hamburger/close toggle button for mobile sidebar.
|
||||||
|
Hidden on desktop via CSS. On click, toggles `data-sidebar-open`
|
||||||
|
on the nearest `.sidebar-layout` ancestor.
|
||||||
|
|
||||||
|
Props:
|
||||||
|
:on-click - click handler (cljs/squint only)
|
||||||
|
:class - additional CSS classes
|
||||||
|
:attrs - additional HTML attributes"
|
||||||
|
[{:keys [on-click class attrs] :as _props}]
|
||||||
|
#?(:squint
|
||||||
|
(let [classes (cond-> (sidebar-mobile-toggle-classes {}) class (str " " class))
|
||||||
|
base-attrs (cond-> (merge {:class classes
|
||||||
|
:type "button"
|
||||||
|
:aria-label "Toggle sidebar"} attrs)
|
||||||
|
on-click (assoc :on-click on-click))]
|
||||||
|
[:button base-attrs
|
||||||
|
[:span {:class "sidebar-toggle-icon-open" :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :menu :size :sm})]
|
||||||
|
[:span {:class "sidebar-toggle-icon-close" :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :x :size :sm})]])
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(let [cls (sidebar-mobile-toggle-class-list {})
|
||||||
|
classes (cond-> cls class (conj class))
|
||||||
|
base-attrs (cond-> (merge {:class classes
|
||||||
|
:type "button"
|
||||||
|
:aria-label "Toggle sidebar"} attrs)
|
||||||
|
on-click (assoc-in [:on :click] on-click))]
|
||||||
|
[:button base-attrs
|
||||||
|
[:span {:class ["sidebar-toggle-icon-open"] :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :menu :size :sm})]
|
||||||
|
[:span {:class ["sidebar-toggle-icon-close"] :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :x :size :sm})]])
|
||||||
|
|
||||||
|
:clj
|
||||||
|
(let [classes (cond-> (sidebar-mobile-toggle-classes {}) class (str " " class))
|
||||||
|
base-attrs (merge {:class classes
|
||||||
|
:type "button"
|
||||||
|
:aria-label "Toggle sidebar"
|
||||||
|
:onclick "this.closest('.sidebar-layout').toggleAttribute('data-sidebar-open')"} attrs)]
|
||||||
|
[:button base-attrs
|
||||||
|
[:span {:class "sidebar-toggle-icon-open" :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :menu :size :sm})]
|
||||||
|
[:span {:class "sidebar-toggle-icon-close" :aria-hidden "true"}
|
||||||
|
(icon/icon {:icon-name :x :size :sm})]])))
|
||||||
|
|
||||||
|
;; ── Sidebar Overlay ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(defn sidebar-overlay
|
||||||
|
"Render the backdrop overlay for mobile sidebar.
|
||||||
|
Clicking it closes the sidebar. Place inside sidebar-layout,
|
||||||
|
as a sibling of the sidebar.
|
||||||
|
|
||||||
|
Props:
|
||||||
|
:on-click - click handler (cljs/squint only)
|
||||||
|
:class - additional CSS classes
|
||||||
|
:attrs - additional HTML attributes"
|
||||||
|
[{:keys [on-click class attrs] :as _props}]
|
||||||
|
#?(:squint
|
||||||
|
(let [classes (cond-> "sidebar-overlay" class (str " " class))
|
||||||
|
base-attrs (cond-> (merge {:class classes :aria-hidden "true"} attrs)
|
||||||
|
on-click (assoc :on-click on-click))]
|
||||||
|
[:div base-attrs])
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(let [classes (cond-> ["sidebar-overlay"] class (conj class))
|
||||||
|
base-attrs (cond-> (merge {:class classes :aria-hidden "true"} attrs)
|
||||||
|
on-click (assoc-in [:on :click] on-click))]
|
||||||
|
[:div base-attrs])
|
||||||
|
|
||||||
|
:clj
|
||||||
|
(let [classes (cond-> "sidebar-overlay" class (str " " class))
|
||||||
|
base-attrs (merge {:class classes
|
||||||
|
:aria-hidden "true"
|
||||||
|
:onclick "this.closest('.sidebar-layout').removeAttribute('data-sidebar-open')"} attrs)]
|
||||||
|
[:div base-attrs])))
|
||||||
|
|
||||||
;; ── Sidebar Separator ───────────────────────────────────────────────
|
;; ── Sidebar Separator ───────────────────────────────────────────────
|
||||||
|
|
||||||
(defn sidebar-separator
|
(defn sidebar-separator
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-layout-main {
|
.sidebar-layout-main {
|
||||||
@@ -144,7 +145,9 @@
|
|||||||
.sidebar-content {
|
.sidebar-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
padding: var(--size-2) var(--size-3);
|
padding: var(--size-2) var(--size-3);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar styling */
|
/* Scrollbar styling */
|
||||||
@@ -196,6 +199,11 @@
|
|||||||
|
|
||||||
/* ── Sidebar Menu Item ─────────────────────────────────────────── */
|
/* ── Sidebar Menu Item ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.sidebar-menu > li {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-menu-item {
|
.sidebar-menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -211,8 +219,12 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.sidebar-menu-item {
|
a.sidebar-menu-item {
|
||||||
@@ -387,3 +399,97 @@ a.sidebar-menu-item {
|
|||||||
border-left: var(--border-0);
|
border-left: var(--border-0);
|
||||||
padding-left: var(--size-2);
|
padding-left: var(--size-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Sidebar Mobile Toggle ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
.sidebar-mobile-toggle {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--size-10);
|
||||||
|
height: var(--size-10);
|
||||||
|
padding: 0;
|
||||||
|
border: var(--border-0);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-0);
|
||||||
|
color: var(--fg-1);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: background-color 150ms ease, color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-mobile-toggle:hover {
|
||||||
|
background: var(--bg-1);
|
||||||
|
color: var(--fg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-mobile-toggle:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show open icon by default, hide close icon */
|
||||||
|
.sidebar-mobile-toggle .sidebar-toggle-icon-open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-mobile-toggle .sidebar-toggle-icon-close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Sidebar Overlay ───────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.sidebar-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 40;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Mobile Responsive ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar-mobile-toggle {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-layout > .sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 50;
|
||||||
|
height: 100vh;
|
||||||
|
height: 100dvh;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 250ms ease;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-layout > .sidebar-overlay {
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open state — toggled via data-sidebar-open on .sidebar-layout */
|
||||||
|
.sidebar-layout[data-sidebar-open] > .sidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
box-shadow: var(--shadow-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-layout[data-sidebar-open] > .sidebar-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swap icons when open */
|
||||||
|
.sidebar-layout[data-sidebar-open] .sidebar-mobile-toggle .sidebar-toggle-icon-open {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-layout[data-sidebar-open] .sidebar-mobile-toggle .sidebar-toggle-icon-close {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -146,6 +146,37 @@
|
|||||||
(is (= :summary (first summary)))
|
(is (= :summary (first summary)))
|
||||||
(is (= "Section" (nth (nth summary 1) 1)))))))
|
(is (= "Section" (nth (nth summary 1) 1)))))))
|
||||||
|
|
||||||
|
(deftest sidebar-mobile-toggle-class-list-test
|
||||||
|
(testing "returns sidebar-mobile-toggle class"
|
||||||
|
(is (= ["sidebar-mobile-toggle"] (sidebar/sidebar-mobile-toggle-class-list {})))))
|
||||||
|
|
||||||
|
(deftest sidebar-mobile-toggle-test
|
||||||
|
(testing "renders a button with toggle icons"
|
||||||
|
(let [result (sidebar/sidebar-mobile-toggle {})]
|
||||||
|
(is (= :button (first result)))
|
||||||
|
(is (= "sidebar-mobile-toggle" (get-in result [1 :class])))
|
||||||
|
(is (= "button" (get-in result [1 :type])))
|
||||||
|
(is (= "Toggle sidebar" (get-in result [1 :aria-label])))))
|
||||||
|
(testing "has onclick for toggling data-sidebar-open"
|
||||||
|
(let [result (sidebar/sidebar-mobile-toggle {})]
|
||||||
|
(is (string? (get-in result [1 :onclick])))))
|
||||||
|
(testing "extra class appended"
|
||||||
|
(let [result (sidebar/sidebar-mobile-toggle {:class "custom"})]
|
||||||
|
(is (= "sidebar-mobile-toggle custom" (get-in result [1 :class]))))))
|
||||||
|
|
||||||
|
(deftest sidebar-overlay-test
|
||||||
|
(testing "renders a div with overlay class"
|
||||||
|
(let [result (sidebar/sidebar-overlay {})]
|
||||||
|
(is (= :div (first result)))
|
||||||
|
(is (= "sidebar-overlay" (get-in result [1 :class])))
|
||||||
|
(is (= "true" (get-in result [1 :aria-hidden])))))
|
||||||
|
(testing "has onclick for closing sidebar"
|
||||||
|
(let [result (sidebar/sidebar-overlay {})]
|
||||||
|
(is (string? (get-in result [1 :onclick])))))
|
||||||
|
(testing "extra class appended"
|
||||||
|
(let [result (sidebar/sidebar-overlay {:class "custom"})]
|
||||||
|
(is (= "sidebar-overlay custom" (get-in result [1 :class]))))))
|
||||||
|
|
||||||
(deftest sidebar-separator-test
|
(deftest sidebar-separator-test
|
||||||
(testing "renders <hr>"
|
(testing "renders <hr>"
|
||||||
(let [result (sidebar/sidebar-separator)]
|
(let [result (sidebar/sidebar-separator)]
|
||||||
|
|||||||
Reference in New Issue
Block a user