docs: add custom section builder and backend testing to Carousel dashboard
- Add custom section builder functionality (Phase 9.3)
- Template for creating filter-based sections with auto-assign rules
- Dynamic rule builder UI (field, operator, value, priority)
- Preview functionality to see matching books before creating
- Integration with existing collections API
- Add TypeScript implementation (Phase 10.3)
- web/src/custom-section-builder.ts
- Procedural style with event delegation
- Rule collection, preview, and form submission
- No duplicate event listeners (delegation only)
- Add backend testing suite (Phase 12)
- Unit tests for dashboard service (filter, reorder)
- Unit tests for dashboard handler (buildSections, helpers)
- Integration tests with test_helpers for API endpoints
- Integration tests for custom collections with auto-assign
- Coverage requirements (>80%)
- Add collections preview endpoint
- POST /api/collections/preview
- Evaluates auto-assign rules against library items
- Returns matching books for preview
- Add /custom-section route
- GET route for custom section builder page
- SSR rendering with libraries selector
- Linked from dashboard settings modal
- Update database schema
- Keep smart_section_types table for 4 default smart sections
- Add collection_items.excluded column for user overrides
- Index on excluded items for performance
- Update verification checklist
- Section 2.2: Add collection_items.excluded verification
- Section 3.4: Add auto-assign rule evaluation verification
- Section 6.3: Add collections preview endpoint verification
- Section 8.5: Add custom section builder template verification
- Section 9.4: Add custom section builder TypeScript verification
- Section 14.4: Add backend tests verification
- Fix duplicate event listener issue
- Removed duplicate change listener for library selector
- Rely on event delegation only for consistency
- Fix buildJSONSections type safety
- Now reuses buildSections() instead of map[string]interface{}
- Better type safety and code reuse
Timeline: 3-4 days dashboard implementation + comprehensive testing
This commit is contained in:
+1284
-1546
File diff suppressed because it is too large
Load Diff
@@ -182,6 +182,14 @@ dropdb test_bookhoard
|
||||
- [ ] Default value is `false`
|
||||
- [ ] Index created on `(user_id, show_on_dashboard) WHERE show_on_dashboard = true`
|
||||
|
||||
**For `collection_items.excluded` column:**
|
||||
|
||||
- [ ] Column added with `ALTER TABLE collection_items ADD COLUMN`
|
||||
- [ ] `IF NOT EXISTS` clause included
|
||||
- [ ] Default value is `false`
|
||||
- [ ] Index created on `(collection_id, excluded) WHERE excluded = true`
|
||||
- [ ] Allows users to exclude auto-assigned items from filter-based collections
|
||||
|
||||
**For `smart_section_types`:**
|
||||
|
||||
- [ ] All required columns exist:
|
||||
@@ -335,6 +343,79 @@ rg "filterHiddenSections|reorderSections" internal/services/dashboard_service.go
|
||||
rg "if len.*== 0" internal/services/dashboard_service.go
|
||||
```
|
||||
|
||||
### 3.4 Verify Auto-Assign Rule Evaluation
|
||||
|
||||
**For `getCollectionSections` method:**
|
||||
|
||||
- [ ] Fetches collections with `show_on_dashboard = true`
|
||||
- [ ] Parses `auto_assign_rules` JSONB from collection
|
||||
- [ ] Fetches all library items via `GetLibraryItems`
|
||||
- [ ] Evaluates rules for each library item using `collectionService.EvaluateRules`
|
||||
- [ ] Merges manual items (not excluded) + auto-matched items
|
||||
- [ ] Filters out excluded items (where `excluded = true`)
|
||||
- [ ] Applies limit after merging
|
||||
- [ ] Only adds collection if it has items in current library
|
||||
|
||||
**Excluded items handling:**
|
||||
- [ ] Manual items query returns `excluded` column from `collection_items`
|
||||
- [ ] Filters out items where `excluded = true`
|
||||
- [ ] Auto-matched items checked against manual items to avoid duplicates
|
||||
- [ ] Excluded items not added even if they match rules
|
||||
|
||||
**Rule evaluation logic:**
|
||||
- [ ] Calls `collectionService.EvaluateRules(item, rules)`
|
||||
- [ ] Checks if `eval.Matches && eval.Confidence > 0.7`
|
||||
- [ ] Adds matching items to collection
|
||||
- [ ] Respects priority (higher priority rules evaluated first)
|
||||
|
||||
**Merge logic:**
|
||||
```go
|
||||
// Pseudo-code for merge logic
|
||||
manualItems := getManualItems(collectionID) // with excluded=false filter
|
||||
autoItems := evaluateAutoAssignRules(libraryItems, rules)
|
||||
finalItems := merge(manualItems, autoItems)
|
||||
finalItems = applyLimit(finalItems, limit)
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check getCollectionSections implementation
|
||||
rg "func.*getCollectionSections" internal/services/dashboard_service.go -A 100
|
||||
|
||||
# Verify auto-assign rules parsing
|
||||
rg "json.Unmarshal.*AutoAssignRules" internal/services/dashboard_service.go
|
||||
|
||||
# Verify EvaluateRules usage
|
||||
rg "collectionService.EvaluateRules" internal/services/dashboard_service.go
|
||||
|
||||
# Check GetLibraryItems query
|
||||
rg "GetLibraryItems" internal/services/dashboard_service.go
|
||||
|
||||
# Verify excluded items filtering
|
||||
rg "Excluded.*Bool" internal/services/dashboard_service.go
|
||||
rg "!item.Excluded.Valid || !item.Excluded.Bool" internal/services/dashboard_service.go
|
||||
|
||||
# Check manual + auto merge logic
|
||||
rg "manualNonExcluded.*append.*autoItems" internal/services/dashboard_service.go
|
||||
|
||||
# Verify limit applied after merge
|
||||
rg "len.*finalItems.*limit" internal/services/dashboard_service.go
|
||||
```
|
||||
|
||||
**Edge case verification:**
|
||||
- [ ] Collection with no auto-assign rules (manual only)
|
||||
- [ ] Collection with auto-assign rules but no matches
|
||||
- [ ] Collection where all manual items are excluded
|
||||
- [ ] Collection with auto-assign rules + manual additions
|
||||
- [ ] Collection with excluded items that match rules
|
||||
- [ ] Large library (performance check)
|
||||
|
||||
**Integration with CollectionService:**
|
||||
- [ ] DashboardService has collectionService dependency
|
||||
- [ ] NewDashboardService creates CollectionService instance
|
||||
- [ ] Reuses existing EvaluateRules from collection_service.go
|
||||
- [ ] No duplicate rule evaluation logic
|
||||
|
||||
---
|
||||
|
||||
## 4. Database Queries Verification
|
||||
@@ -593,7 +674,86 @@ rg 'GET.*"/settings"' internal/router/frontend.go -A 20
|
||||
rg 'POST.*"/settings"' internal/router/frontend.go -A 40
|
||||
```
|
||||
|
||||
### 6.3 Verify Config Setup
|
||||
**For `/custom-section` route:**
|
||||
|
||||
- [ ] GET `/custom-section` → renders custom section builder form
|
||||
- [ ] Passes libraries for selector
|
||||
- [ ] Renders `templates.CustomSectionBuilder`
|
||||
- [ ] Links from dashboard settings modal
|
||||
- [ ] No POST route (form submits via JSON to `/api/collections`)
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check custom-section route
|
||||
rg 'GET.*"/custom-section"' internal/router/frontend.go -A 20
|
||||
|
||||
# Verify template rendering
|
||||
rg "templates.CustomSectionBuilder" internal/router/frontend.go
|
||||
|
||||
# Verify libraries passed to template
|
||||
rg "CustomSectionBuilder.*libraries" internal/router/frontend.go
|
||||
```
|
||||
|
||||
### 6.3 Verify Collections Preview Endpoint
|
||||
|
||||
**For `internal/handlers/collections.go`:**
|
||||
|
||||
- [ ] `PreviewAutoAssignRules` method exists
|
||||
- [ ] POST `/api/collections/preview` route registered
|
||||
- [ ] Accepts library_id, rules, limit in request body
|
||||
- [ ] Evaluates rules against library items
|
||||
- [ ] Returns matching books with count
|
||||
- [ ] Uses existing `collectionService.EvaluateRules()`
|
||||
- [ ] Returns `handlers.BookInfo` format
|
||||
|
||||
**Request format:**
|
||||
```json
|
||||
{
|
||||
"library_id": "uuid",
|
||||
"rules": [
|
||||
{
|
||||
"id": "rule1",
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Sci-Fi",
|
||||
"priority": 5
|
||||
}
|
||||
],
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
|
||||
**Response format:**
|
||||
```json
|
||||
{
|
||||
"books": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"title": "Dune",
|
||||
"author": "Frank Herbert",
|
||||
"cover_image_path": "/path/to/cover.jpg"
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check handler method exists
|
||||
rg "func.*PreviewAutoAssignRules" internal/handlers/collections.go -A 30
|
||||
|
||||
# Check route registration
|
||||
rg 'POST.*"/preview"' internal/router/collections.go
|
||||
|
||||
# Verify EvaluateRules usage
|
||||
rg "collectionService.EvaluateRules" internal/handlers/collections.go
|
||||
|
||||
# Check BookInfo conversion
|
||||
rg "handlers.BookInfo" internal/handlers/collections.go
|
||||
```
|
||||
|
||||
### 6.4 Verify Config Setup
|
||||
|
||||
**For `internal/router/router.go`:**
|
||||
|
||||
@@ -853,29 +1013,36 @@ rg 'aria-|role=|tabindex' templates/
|
||||
rg 'data-(action|section|book)=' templates/
|
||||
```
|
||||
|
||||
### 8.3 Verify HTMX Integration
|
||||
### 8.3 Verify Library Selector (TypeScript, Not HTMX)
|
||||
|
||||
**HTMX attributes:**
|
||||
**Library selector attributes:**
|
||||
|
||||
- [ ] Library selector uses `hx-get="/dashboard/sections"`
|
||||
- [ ] `hx-target="#sections-container"`
|
||||
- [ ] `hx-indicator="#loading-spinner"`
|
||||
- [ ] `hx-swap="innerHTML"`
|
||||
- [ ] Forms have fallback `action` and `method`
|
||||
- [ ] Select element has `id="library-select"` and `name="library_id"`
|
||||
- [ ] NO HTMX attributes (no `hx-get`, `hx-target`, etc.)
|
||||
- [ ] Uses `data-action="switch-library"` or change event listener
|
||||
- [ ] Options rendered from SSR data (`libraries` parameter)
|
||||
- [ ] Current library selected by default
|
||||
|
||||
**Partial template:**
|
||||
**Loading indicator:**
|
||||
|
||||
- [ ] `DashboardSectionsPartial` exists
|
||||
- [ ] Renders only sections (no full page)
|
||||
- [ ] Used by HTMX swap
|
||||
- [ ] `#loading-spinner` element exists (hidden by default)
|
||||
- [ ] Used by TypeScript during library switching
|
||||
- [ ] Shows/hides via CSS class manipulation
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check HTMX attributes
|
||||
rg 'hx-(get|post|target|swap|indicator)=' templates/
|
||||
# Check library selector (should NOT have HTMX)
|
||||
rg 'id="library-select"' templates/dashboard.templ -A 5
|
||||
|
||||
# Verify partial template
|
||||
rg "templ DashboardSectionsPartial" templates/ -A 10
|
||||
# Verify NO HTMX attributes on library selector
|
||||
rg 'id="library-select"' templates/dashboard.templ -A 5 | rg 'hx-'
|
||||
# Should return nothing
|
||||
|
||||
# Check loading indicator exists
|
||||
rg 'loading-spinner' templates/dashboard.templ
|
||||
|
||||
# Verify data-action for library switching
|
||||
rg 'data-action="switch-library"' templates/dashboard.templ
|
||||
```
|
||||
|
||||
### 8.4 Verify Settings Template
|
||||
@@ -904,6 +1071,100 @@ rg 'data-action="save-settings"' templates/settings.templ
|
||||
rg "database\.(Users|UserDashboardPreferences)" templates/settings.templ
|
||||
```
|
||||
|
||||
### 8.5 Verify Custom Section Builder Template
|
||||
|
||||
**For `templates/custom_section.templ`:**
|
||||
|
||||
- [ ] SSR page for creating filter-based custom sections
|
||||
- [ ] Uses **handler types** (libraries, user)
|
||||
- [ ] Section details form (name, description, library selector)
|
||||
- [ ] Auto-assign rules section with dynamic rule addition
|
||||
- [ ] Preview section showing matching books
|
||||
- [ ] Form uses `data-action="create-custom-section"`
|
||||
- [ ] Cancel button uses `data-action="cancel-create-section"`
|
||||
- [ ] TailwindCSS only
|
||||
- [ ] Event delegation
|
||||
- [ ] Includes required scripts (custom-section-builder.js)
|
||||
- [ ] Links to `/custom-section` route
|
||||
|
||||
**Form structure verification:**
|
||||
- [ ] Name input (required)
|
||||
- [ ] Description textarea (optional)
|
||||
- [ ] Library selector (required, populated from SSR)
|
||||
- [ ] Rules container with `id="rules-container"`
|
||||
- [ ] "Add Rule" button with `data-action="add-rule"`
|
||||
- [ ] Preview button with `data-action="preview-section"`
|
||||
- [ ] Submit button with `data-action="create-custom-section"`
|
||||
- [ ] Cancel button with `data-action="cancel-create-section"`
|
||||
|
||||
**Rule fields (dynamically added via JavaScript):**
|
||||
- [ ] Field selector (genre, author, series, language, publisher, copyright_year, tags)
|
||||
- [ ] Operator selector (equals, contains, starts_with, ends_with, greater_than, less_than)
|
||||
- [ ] Value input (text)
|
||||
- [ ] Priority input (number, 1-10)
|
||||
- [ ] Remove button with `data-action="remove-rule"` and `data-rule-id`
|
||||
|
||||
**Preview section:**
|
||||
- [ ] Preview container with `id="preview-container"`
|
||||
- [ ] Shows loading state while fetching
|
||||
- [ ] Displays matching books in grid layout
|
||||
- [ ] Shows count of matching books
|
||||
- [ ] Handles empty results gracefully
|
||||
|
||||
**Dashboard settings modal enhancement:**
|
||||
- [ ] "Create Custom Section" button in settings modal
|
||||
- [ ] Links to `/custom-section` page
|
||||
- [ ] Has description explaining what custom sections are
|
||||
- [ ] Styled with accent color
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check template exists
|
||||
ls -la templates/custom_section.templ
|
||||
|
||||
# Check template structure
|
||||
rg "templ CustomSectionBuilder" templates/custom_section.templ -A 100
|
||||
|
||||
# Verify SSR data (libraries parameter)
|
||||
rg "CustomSectionBuilder.*libraries" templates/custom_section.templ
|
||||
|
||||
# Verify data-action attributes
|
||||
rg 'data-action="(add-rule|remove-rule|preview-section|create-custom-section|cancel-create-section)"' templates/custom_section.templ
|
||||
|
||||
# Verify form fields
|
||||
rg 'name="name".*required' templates/custom_section.templ
|
||||
rg 'name="library_id".*required' templates/custom_section.templ
|
||||
rg 'name="description"' templates/custom_section.templ
|
||||
|
||||
# Verify rules container
|
||||
rg 'id="rules-container"' templates/custom_section.templ
|
||||
|
||||
# Verify preview container
|
||||
rg 'id="preview-container"' templates/custom_section.templ
|
||||
|
||||
# Verify TailwindCSS only (no custom CSS)
|
||||
rg '<style' templates/custom_section.templ
|
||||
# Should return nothing
|
||||
|
||||
# Verify event delegation (no inline onclick)
|
||||
rg 'onclick=' templates/custom_section.templ
|
||||
# Should return nothing
|
||||
|
||||
# Verify script includes
|
||||
rg 'custom-section-builder.js' templates/custom_section.templ
|
||||
|
||||
# Check dashboard settings modal has Create Custom Section button
|
||||
rg 'Create Custom Section' templates/dashboard.templ
|
||||
rg '/custom-section' templates/dashboard.templ
|
||||
```
|
||||
|
||||
**JavaScript integration verification:**
|
||||
- [ ] No duplicate change event listener (library select uses delegation only)
|
||||
- [ ] Event delegation handles all form actions
|
||||
- [ ] Rules dynamically added via `insertAdjacentHTML`
|
||||
- [ ] Form submission prevented, JSON sent via API
|
||||
- [ ] Preview updates DOM without page reload
|
||||
|
||||
---
|
||||
|
||||
## 9. TypeScript Implementation Verification
|
||||
@@ -938,15 +1199,20 @@ rg "import type " web/src/dashboard.ts
|
||||
rg "api\.|showToast\.|events\." web/src/dashboard.ts
|
||||
```
|
||||
|
||||
### 9.2 Verify Carousel Functionality
|
||||
### 9.2 Verify TypeScript Dashboard Functions
|
||||
|
||||
**Required functions:**
|
||||
|
||||
- [ ] `scrollCarousel(sectionId: string, direction: number): void`
|
||||
- [ ] `switchLibrary(libraryId: string): Promise<void>` - Fetches JSON and re-renders sections
|
||||
- [ ] `renderSections(sections: SectionData[]): void` - Renders sections from JSON
|
||||
- [ ] `renderBookCard(book: BookInfo): string` - Renders single book card HTML
|
||||
- [ ] `openDashboardSettings(): void`
|
||||
- [ ] `closeDashboardSettings(): void`
|
||||
- [ ] `saveDashboardSettings(): void`
|
||||
- [ ] `saveDashboardSettings(): Promise<void>`
|
||||
- [ ] `toggleSectionVisibility(sectionId: string): void`
|
||||
- [ ] `viewBook(bookId: string): Promise<void>`
|
||||
- [ ] `reloadPage(): void`
|
||||
- [ ] `updateItemsCount(count: number): void`
|
||||
- [ ] `viewBook(bookId: string): void`
|
||||
- [ ] `reloadPage(): void`
|
||||
@@ -980,16 +1246,18 @@ rg "showToast\.(error|success|info)" web/src/dashboard.ts
|
||||
**Event listeners:**
|
||||
|
||||
- [ ] Single click listener for all dashboard actions
|
||||
- [ ] Library select change event listener (for `switchLibrary`)
|
||||
- [ ] Uses `data-action` attributes
|
||||
- [ ] Handles:
|
||||
- [ ] `scroll-carousel`
|
||||
- [ ] `open-dashboard-settings`
|
||||
- [ ] `close-dashboard-settings`
|
||||
- [ ] `save-dashboard-settings`
|
||||
- [ ] `toggle-section-visibility`
|
||||
- [ ] `update-items-count`
|
||||
- [ ] `view-book`
|
||||
- [ ] `reload-page`
|
||||
- [ ] `switch-library` (library select changes)
|
||||
- [ ] `scroll-carousel`
|
||||
- [ ] `open-dashboard-settings`
|
||||
- [ ] `close-dashboard-settings`
|
||||
- [ ] `save-dashboard-settings`
|
||||
- [ ] `toggle-section-visibility`
|
||||
- [ ] `update-items-count`
|
||||
- [ ] `view-book`
|
||||
- [ ] `reload-page`
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
@@ -1025,6 +1293,75 @@ rg "addEventListener\('submit'" web/src/settings.ts
|
||||
rg "validity|checkValidity" web/src/settings.ts
|
||||
```
|
||||
|
||||
### 9.4 Verify Custom Section Builder TypeScript
|
||||
|
||||
**For `web/src/custom-section-builder.ts`:**
|
||||
|
||||
- [ ] Procedural style (no classes, no `this`)
|
||||
- [ ] Functions exported to `window` object
|
||||
- [ ] Event delegation via `data-action` attributes
|
||||
- [ ] Dynamic rule addition/removal
|
||||
- [ ] Rule interface matches Go services.Rule
|
||||
- [ ] Uses shared API client and toast
|
||||
- [ ] Preview functionality with loading states
|
||||
|
||||
**Required functions:**
|
||||
|
||||
- [ ] `addRule(): void` - Adds new rule row to form
|
||||
- [ ] `removeRule(ruleId: string): void` - Removes rule row
|
||||
- [ ] `collectRules(): Rule[]` - Collects all rules from form
|
||||
- [ ] `previewSection(): Promise<void>` - Calls preview API, displays results
|
||||
- [ ] `createCustomSection(): Promise<void>` - Creates collection with rules
|
||||
- [ ] `cancelCreateSection(): void` - Navigates back to dashboard
|
||||
- [ ] `initializeCustomSectionBuilder(): void` - Sets up event delegation
|
||||
|
||||
**Rule structure verification:**
|
||||
- [ ] Rule interface has: id, field, operator, value, priority
|
||||
- [ ] Field options: genre, author, series, language, publisher, copyright_year, tags
|
||||
- [ ] Operator options: equals, contains, starts_with, ends_with, greater_than, less_than
|
||||
- [ ] Priority: 1-10 (default 5)
|
||||
|
||||
**Preview functionality:**
|
||||
- [ ] Validates library_id is selected
|
||||
- [ ] Validates at least one rule exists
|
||||
- [ ] Calls `/api/collections/preview` endpoint
|
||||
- [ ] Displays matching books in grid
|
||||
- [ ] Shows count of matches
|
||||
- [ ] Handles loading state
|
||||
- [ ] Handles errors with toast notification
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check file exists
|
||||
ls -la web/src/custom-section-builder.ts
|
||||
|
||||
# Check for OOP patterns (should find nothing)
|
||||
rg "class |this\." web/src/custom-section-builder.ts
|
||||
|
||||
# Verify Rule interface
|
||||
rg "interface Rule" web/src/custom-section-builder.ts -A 10
|
||||
|
||||
# Check required functions exist
|
||||
rg "^function (addRule|removeRule|collectRules|previewSection|createCustomSection|cancelCreateSection)" web/src/custom-section-builder.ts
|
||||
|
||||
# Verify API integration
|
||||
rg "api\.post.*collections/preview" web/src/custom-section-builder.ts
|
||||
|
||||
# Verify toast usage
|
||||
rg "showToast\.(error|success|info)" web/src/custom-section-builder.ts
|
||||
|
||||
# Verify rule HTML generation
|
||||
rg "ruleHTML|insertAdjacentHTML" web/src/custom-section-builder.ts
|
||||
|
||||
# Verify event delegation
|
||||
rg "addEventListener\('click'" web/src/custom-section-builder.ts -A 50
|
||||
```
|
||||
|
||||
**Form verification:**
|
||||
- [ ] No duplicate change event listener (only delegation handles library select)
|
||||
- [ ] Event delegation handles all actions
|
||||
- [ ] Actions: add-rule, remove-rule, preview-section, create-custom-section, cancel-create-section
|
||||
|
||||
### 9.5 Verify TypeScript Compilation
|
||||
|
||||
**Build verification:**
|
||||
@@ -1347,56 +1684,79 @@ rg 'overflow-x-auto' templates/
|
||||
|
||||
---
|
||||
|
||||
## 12. Progressive Enhancement Verification
|
||||
## 12. SSR + TypeScript Hybrid Verification
|
||||
|
||||
### 12.1 Verify Works Without JavaScript
|
||||
### 12.1 Verify SSR Initial Load
|
||||
|
||||
**Critical paths:**
|
||||
|
||||
- [ ] Dashboard loads with SSR data
|
||||
- [ ] Library switcher uses HTMX (works without custom JS)
|
||||
- [ ] Settings form submits with full page reload
|
||||
- [ ] All data visible on initial load
|
||||
- [ ] No content hidden behind JavaScript
|
||||
- [ ] Dashboard loads with pre-populated SSR data (sections rendered by server)
|
||||
- [ ] All sections and books visible in initial HTML (view source to verify)
|
||||
- [ ] No client-side fetching on initial page load
|
||||
- [ ] Library selector options rendered server-side
|
||||
- [ ] Current library pre-selected in HTML
|
||||
|
||||
**Test procedure:**
|
||||
1. Open DevTools → Disable JavaScript
|
||||
2. Navigate to `/dashboard`
|
||||
3. Verify:
|
||||
- [ ] All sections render
|
||||
- [ ] All books visible
|
||||
- [ ] Library selector works (HTMX)
|
||||
- [ ] Settings form submits
|
||||
4. Re-enable JavaScript
|
||||
5. Verify enhanced behavior works
|
||||
1. Open DevTools → Network tab
|
||||
2. Navigate to `/dashboard?library_id=<id>`
|
||||
3. Verify in HTML response:
|
||||
- [ ] Sections present in initial HTML (not added via JS)
|
||||
- [ ] Books present in initial HTML
|
||||
- [ ] Library selector has all options
|
||||
4. View page source (Ctrl+U) and verify:
|
||||
- [ ] Section HTML present in source
|
||||
- [ ] Book cards present in source
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check template has SSR data
|
||||
rg "templ Dashboard" templates/dashboard.templ -A 5
|
||||
# Should have sections parameter
|
||||
# Check template has sections parameter (SSR)
|
||||
rg "templ Dashboard.*sections" templates/dashboard.templ -A 5
|
||||
|
||||
# Check for client-side data fetching (should be minimal)
|
||||
rg "fetch\(" web/src/dashboard.ts
|
||||
# Should only be for updates, not initial load
|
||||
# Verify frontend route passes data to template
|
||||
rg "templates.Dashboard.*sections" internal/router/frontend.go -A 5
|
||||
|
||||
# Check for initial load fetching (should be NONE)
|
||||
rg "fetch.*onload|DOMContentLoaded.*fetch" web/src/dashboard.ts
|
||||
# Should return nothing - no fetch on initial load
|
||||
```
|
||||
|
||||
### 12.2 Verify HTMX Fallbacks
|
||||
### 12.2 Verify TypeScript Updates
|
||||
|
||||
**All HTMX attributes:**
|
||||
**Critical paths:**
|
||||
|
||||
- [ ] Forms have `action` and `method` (non-JS fallback)
|
||||
- [ ] Links have `href` (non-JS fallback)
|
||||
- [ ] HTMX attributes enhance but don't replace
|
||||
- [ ] No content requires JavaScript to function
|
||||
- [ ] Library switching triggers JSON fetch
|
||||
- [ ] Loading indicator shows during fetch
|
||||
- [ ] Sections re-render after library switch
|
||||
- [ ] Settings save via API, then page reload
|
||||
- [ ] Error handling with toast notifications
|
||||
|
||||
**Test procedure:**
|
||||
1. Load dashboard (verify SSR data present)
|
||||
2. Change library selector
|
||||
3. Verify:
|
||||
- [ ] Loading spinner appears
|
||||
- [ ] Network tab shows `/api/dashboard/sections?library_id=<new>` request
|
||||
- [ ] Sections update without full page reload
|
||||
- [ ] URL updates with `?library_id=<new>`
|
||||
4. Open settings, change items per section
|
||||
5. Verify:
|
||||
- [ ] API POST to `/dashboard/settings`
|
||||
- [ ] Success toast appears
|
||||
- [ ] Page reloads with new settings applied
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check HTMX forms have standard attributes
|
||||
rg '<form' templates/ -A 3 | rg 'action=|method='
|
||||
# Check TypeScript has switchLibrary function
|
||||
rg "function switchLibrary|async switchLibrary" web/src/dashboard.ts -A 10
|
||||
|
||||
# Check links have href
|
||||
rg '<a ' templates/ | rg 'href='
|
||||
# Verify it fetches JSON
|
||||
rg "fetch.*api/dashboard/sections" web/src/dashboard.ts
|
||||
|
||||
# Verify renderSections function exists
|
||||
rg "function renderSections" web/src/dashboard.ts -A 10
|
||||
|
||||
# Check settings API call
|
||||
rg "api.post.*settings|fetch.*settings" web/src/dashboard.ts
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1603,6 +1963,78 @@ rg "backdrop-filter|aspect-ratio|snap" templates/
|
||||
# Verify fallbacks if needed
|
||||
```
|
||||
|
||||
### 14.4 Verify Backend Tests Exist
|
||||
|
||||
**Unit tests for dashboard service:**
|
||||
|
||||
- [ ] `internal/services/dashboard_service_test.go` exists
|
||||
- [ ] Tests for:
|
||||
- [ ] `TestDashboardService_FilterHiddenSections` - Tests section filtering
|
||||
- [ ] `TestDashboardService_ReorderSections` - Tests custom ordering
|
||||
- [ ] `TestDashboardService_GetSectionItems` - Validates method signature
|
||||
- [ ] Uses testify/assert and testify/require
|
||||
- [ ] Tests cover edge cases (empty arrays, nil values, duplicates)
|
||||
|
||||
**Unit tests for dashboard handler:**
|
||||
|
||||
- [ ] `internal/handlers/dashboard_handler_test.go` exists
|
||||
- [ ] Tests for:
|
||||
- [ ] `TestBuildSections` - Tests conversion from service to handler types
|
||||
- [ ] `TestGetSectionHelpers` - Tests helper functions
|
||||
- [ ] getSectionType, getSectionTitle, getSectionIcon
|
||||
- [ ] Validates pgtype field conversion
|
||||
|
||||
**Integration tests with test_helpers:**
|
||||
|
||||
- [ ] `internal/handlers/dashboard_integration_test.go` exists
|
||||
- [ ] Uses `test_helpers.TestSuite` for database setup/teardown
|
||||
- [ ] Tests for:
|
||||
- [ ] `TestGetSections` - Full dashboard sections API
|
||||
- [ ] `TestGetSections_UserPreferences` - Preferences (hidden, order, limits)
|
||||
- [ ] `TestGetSections_CustomCollections` - Auto-assign rules
|
||||
- [ ] `TestGetSections_Validation` - Error handling (400 errors)
|
||||
- [ ] Creates test fixtures (users, libraries, media items)
|
||||
- [ ] Tests reading progress (in progress, completed, unread)
|
||||
- [ ] Tests manual + auto-matched items merging
|
||||
- [ ] Tests excluded items filtering
|
||||
|
||||
**Integration tests for collections:**
|
||||
|
||||
- [ ] `internal/handlers/collections_integration_test.go` modified
|
||||
- [ ] Adds:
|
||||
- [ ] `TestPreviewAutoAssignRules` - Tests rule preview endpoint
|
||||
- [ ] `TestCreateCollectionWithAutoAssign` - Tests collection creation
|
||||
- [ ] Validates genre filtering works
|
||||
- [ ] Validates auto-assign rules persist to database
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check test files exist
|
||||
ls -la internal/services/dashboard_service_test.go
|
||||
ls -la internal/handlers/dashboard_handler_test.go
|
||||
ls -la internal/handlers/dashboard_integration_test.go
|
||||
|
||||
# Run tests
|
||||
go test ./internal/services/dashboard_service_test.go -v
|
||||
go test ./internal/handlers/dashboard_handler_test.go -v
|
||||
go test ./internal/handlers/dashboard_integration_test.go -v
|
||||
|
||||
# Run with coverage
|
||||
go test ./internal/... -cover -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
# Verify test helpers usage
|
||||
rg "test_helpers.TestSuite" internal/handlers/dashboard_integration_test.go
|
||||
rg "CreateTestUser|CreateTestLibrary|CreateTestMediaItem" internal/handlers/dashboard_integration_test.go
|
||||
```
|
||||
|
||||
**Test coverage requirements:**
|
||||
- [ ] Unit tests for all service methods (filterHiddenSections, reorderSections)
|
||||
- [ ] Unit tests for all helper functions
|
||||
- [ ] Integration tests for all API endpoints
|
||||
- [ ] Integration tests use test_helpers
|
||||
- [ ] Coverage > 80% for new code
|
||||
|
||||
---
|
||||
|
||||
## 15. Cross-Reference Verification
|
||||
|
||||
Reference in New Issue
Block a user