fix(podcasts): code-review pass over the session's UI surfaces
Findings from the systematic gotcha-class review before the browser
pass:
- The collection smart editor's Apply derived the collection NAME from
document.title — which parses to "Smart Collection", RENAMING the
collection on every apply. The server now renders the real name
(data-collection-name) and the write path refuses to proceed without
it.
- TestRenderingPartialsRender pinned the pre-episode-collections
empty-state copy ("No books in this collection yet.") — updated to
the shipped label ("Nothing in this collection yet."; collections
hold episodes now).
- The deleted handler-local rule comparators (evaluateRule/
compareValues — removed in item 3.4's single-evaluator refactor)
left behind two unit-test files testing the duplicates; deleted.
Their semantics remain covered at the service level
(TestEvaluateRules_* over FieldMatches/EvaluateRules) and
TestTextToString (which actually pins utils.PgTextString) stays.
Everything else reviewed clean against the gotcha classes: templ
whole-expression attrs, ?={ boolean attrs, JSONArg quoting in
x-init/data attrs, Alpine CSP store paths, htmx form-encoding, the
" assert escaping, and the aria-label selector contract on the
player's reload listener.
This commit is contained in:
1 parent
94bd69d22d
commit
c54430c254
6 files changed
+75
-552
No files matched your search
@@ -1,415 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bookhoard/internal/database"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test collection rule matching with various operators
|
|
||||||
|
|
||||||
func TestEvaluateRule_Equals_StringMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_Equals_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Fantasy", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_NotEquals_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Fantasy", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "not_equals", "Science Fiction")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_NotEquals_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "not_equals", "Science Fiction")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_Contains_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "author", "contains", "asimov")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_Contains_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "author", "contains", "clarke")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_NotContains_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "author", "not_contains", "clarke")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_StartsWith_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Series: pgtype.Text{String: "Foundation", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "series", "starts_with", "Foun")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_StartsWith_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Series: pgtype.Text{String: "Foundation", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "series", "starts_with", "Robots")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_EndsWith_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Series: pgtype.Text{String: "Foundation", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "series", "ends_with", "tion")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_GreaterThan_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "copyright_year", "greater_than", "2000")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_GreaterThan_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 1999, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "copyright_year", "greater_than", "2000")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_LessThan_Matches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 1999, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "copyright_year", "less_than", "2000")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_LessThan_NoMatch(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "copyright_year", "less_than", "2000")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_NullField(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{Valid: false}, // NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
|
|
||||||
assert.False(t, result, "Should return false for NULL fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_CaseInsensitive(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("Science Fiction", "contains", "science fiction")
|
|
||||||
assert.True(t, result, "Contains should be case-insensitive")
|
|
||||||
|
|
||||||
result = handler.compareValues("Science Fiction", "contains", "SCIENCE FICTION")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_EmptyString(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("", "contains", "test")
|
|
||||||
assert.False(t, result, "Empty string should not contain anything")
|
|
||||||
|
|
||||||
result = handler.compareValues("test", "contains", "")
|
|
||||||
assert.True(t, result, "Non-empty string contains empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_NumericEdgeCases(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("0", "greater_than", "-1")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("9999", "less_than", "10000")
|
|
||||||
assert.True(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test rule checking with multiple rules
|
|
||||||
|
|
||||||
func TestCheckRulesAgainstBook_MatchesFirstRule(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"field": "genre",
|
|
||||||
"operator": "equals",
|
|
||||||
"value": "Science Fiction",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "author",
|
|
||||||
"operator": "contains",
|
|
||||||
"value": "Asimov",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
reason := handler.checkRulesAgainstBook(item, rules)
|
|
||||||
assert.NotEmpty(t, reason, "Should match at least one rule")
|
|
||||||
assert.Contains(t, reason, "genre")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckRulesAgainstBook_MatchesSecondRule(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Fantasy", Valid: true},
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"field": "genre",
|
|
||||||
"operator": "equals",
|
|
||||||
"value": "Science Fiction",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "author",
|
|
||||||
"operator": "contains",
|
|
||||||
"value": "Asimov",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
reason := handler.checkRulesAgainstBook(item, rules)
|
|
||||||
assert.NotEmpty(t, reason, "Should match second rule")
|
|
||||||
assert.Contains(t, reason, "author")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckRulesAgainstBook_NoMatches(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Mystery", Valid: true},
|
|
||||||
Author: pgtype.Text{String: "Agatha Christie", Valid: true},
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 1950, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"field": "genre",
|
|
||||||
"operator": "equals",
|
|
||||||
"value": "Science Fiction",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": "author",
|
|
||||||
"operator": "contains",
|
|
||||||
"value": "Asimov",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
reason := handler.checkRulesAgainstBook(item, rules)
|
|
||||||
assert.Empty(t, reason, "Should not match any rules")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckRulesAgainstBook_EmptyRules(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
rules := []map[string]interface{}{}
|
|
||||||
|
|
||||||
reason := handler.checkRulesAgainstBook(item, rules)
|
|
||||||
assert.Empty(t, reason, "Empty rules should return empty reason")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test complex rule scenarios
|
|
||||||
|
|
||||||
func TestComplexRule_MultipleConditions(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
// Book: "Foundation" by Isaac Asimov, Sci-Fi, 1951
|
|
||||||
item := database.MediaItems{
|
|
||||||
Title: "Foundation",
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 1951, Valid: true},
|
|
||||||
Series: pgtype.Text{String: "Foundation", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
field string
|
|
||||||
operator string
|
|
||||||
value string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Match genre exactly",
|
|
||||||
field: "genre",
|
|
||||||
operator: "equals",
|
|
||||||
value: "Science Fiction",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Match author substring",
|
|
||||||
field: "author",
|
|
||||||
operator: "contains",
|
|
||||||
value: "asimov",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Match year greater than",
|
|
||||||
field: "copyright_year",
|
|
||||||
operator: "greater_than",
|
|
||||||
value: "1950",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Match year less than",
|
|
||||||
field: "copyright_year",
|
|
||||||
operator: "less_than",
|
|
||||||
value: "2000",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Match series starts with",
|
|
||||||
field: "series",
|
|
||||||
operator: "starts_with",
|
|
||||||
value: "Found",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Match series ends with",
|
|
||||||
field: "series",
|
|
||||||
operator: "ends_with",
|
|
||||||
value: "tion",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "No match for genre",
|
|
||||||
field: "genre",
|
|
||||||
operator: "equals",
|
|
||||||
value: "Fantasy",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "No match for author",
|
|
||||||
field: "author",
|
|
||||||
operator: "contains",
|
|
||||||
value: "Tolkien",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := handler.evaluateRule(item, tt.field, tt.operator, tt.value)
|
|
||||||
assert.Equal(t, tt.expected, result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test edge cases and error handling
|
|
||||||
|
|
||||||
func TestEvaluateRule_InvalidOperator(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "invalid_operator", "Science Fiction")
|
|
||||||
assert.False(t, result, "Invalid operator should return false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_NonExistentField(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "nonexistent_field", "equals", "value")
|
|
||||||
assert.False(t, result, "Non-existent field should return false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_InvalidOperator(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("test", "invalid_op", "value")
|
|
||||||
assert.False(t, result, "Invalid comparison operator should return false")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_NumericConversion(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
// Valid numeric strings
|
|
||||||
result := handler.compareValues("100", "greater_than", "99")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("99", "less_than", "100")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
// Invalid numeric strings (should return false)
|
|
||||||
result = handler.compareValues("abc", "greater_than", "100")
|
|
||||||
assert.False(t, result, "Non-numeric string should not compare as greater than")
|
|
||||||
|
|
||||||
result = handler.compareValues("100", "greater_than", "xyz")
|
|
||||||
assert.False(t, result, "Non-numeric value should not allow comparison")
|
|
||||||
}
|
|
||||||
@@ -3,95 +3,12 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"bookhoard/internal/database"
|
|
||||||
"bookhoard/internal/utils"
|
"bookhoard/internal/utils"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCompareValues_Equals(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("Science Fiction", "equals", "Science Fiction")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("Science Fiction", "equals", "Fantasy")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_Contains(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("Isaac Asimov", "contains", "asimov")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("Isaac Asimov", "contains", "clarke")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_GreaterThan(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("2021", "greater_than", "2000")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("1999", "greater_than", "2000")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompareValues_NotEquals(t *testing.T) {
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.compareValues("Science Fiction", "not_equals", "Fantasy")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.compareValues("Science Fiction", "not_equals", "Science Fiction")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_Genre(t *testing.T) {
|
|
||||||
item := database.MediaItems{
|
|
||||||
Genre: pgtype.Text{String: "Science Fiction", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "genre", "equals", "Science Fiction")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.evaluateRule(item, "genre", "equals", "Fantasy")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_Author(t *testing.T) {
|
|
||||||
item := database.MediaItems{
|
|
||||||
Author: pgtype.Text{String: "Isaac Asimov", Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "author", "contains", "asimov")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.evaluateRule(item, "author", "contains", "clarke")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvaluateRule_CopyrightYear(t *testing.T) {
|
|
||||||
item := database.MediaItems{
|
|
||||||
CopyrightYear: pgtype.Int4{Int32: 2021, Valid: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := &CollectionHandler{}
|
|
||||||
|
|
||||||
result := handler.evaluateRule(item, "copyright_year", "greater_than", "2000")
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
result = handler.evaluateRule(item, "copyright_year", "less_than", "2000")
|
|
||||||
assert.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTextToString(t *testing.T) {
|
func TestTextToString(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ templ CollectionRules(user User, collection CollectionData, fieldsJSON string, t
|
|||||||
<body
|
<body
|
||||||
class={ "theme-" + user.Theme }
|
class={ "theme-" + user.Theme }
|
||||||
x-data="collectionRules"
|
x-data="collectionRules"
|
||||||
|
data-collection-name={ collection.Name }
|
||||||
x-init={ "initCollectionRules('" + collection.ID + "', '" + scope + "', " + JSONArg(treeJSON) + ")" }
|
x-init={ "initCollectionRules('" + collection.ID + "', '" + scope + "', " + JSONArg(treeJSON) + ")" }
|
||||||
>
|
>
|
||||||
@Header(user, "/collections")
|
@Header(user, "/collections")
|
||||||
|
|||||||
@@ -71,20 +71,33 @@ func CollectionRules(user User, collection CollectionData, fieldsJSON string, tr
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" x-data=\"collectionRules\" x-init=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" x-data=\"collectionRules\" data-collection-name=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var5 string
|
var templ_7745c5c3_Var5 string
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue("initCollectionRules('" + collection.ID + "', '" + scope + "', " + JSONArg(treeJSON) + ")")
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(collection.Name)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 23, Col: 102}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 23, Col: 41}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" x-init=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue("initCollectionRules('" + collection.ID + "', '" + scope + "', " + JSONArg(treeJSON) + ")")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 24, Col: 102}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -92,7 +105,7 @@ func CollectionRules(user User, collection CollectionData, fieldsJSON string, tr
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><div class=\"mb-6\"><button @click=\"backToCollection()\" class=\"btn btn-secondary mb-4\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><div class=\"mb-6\"><button @click=\"backToCollection()\" class=\"btn btn-secondary mb-4\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -100,100 +113,100 @@ func CollectionRules(user User, collection CollectionData, fieldsJSON string, tr
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Back to Collection</button><div class=\"flex items-center gap-4\"><span class=\"grid place-items-center h-12 w-12 rounded-2xl shrink-0 text-2xl\" style=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Back to Collection</button><div class=\"flex items-center gap-4\"><span class=\"grid place-items-center h-12 w-12 rounded-2xl shrink-0 text-2xl\" style=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: var(--accent-muted); color: " + collection.Color)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 33, Col: 159}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var7 string
|
var templ_7745c5c3_Var7 string
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon)
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: var(--accent-muted); color: " + collection.Color)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 33, Col: 179}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 34, Col: 159}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var8 string
|
var templ_7745c5c3_Var8 string
|
||||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Icon)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 35, Col: 90}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 34, Col: 179}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</h1><p class=\"flex items-center gap-2\" style=\"color: var(--text-secondary)\"><span class=\"badge text-xs\" style=\"background-color: var(--accent-muted); color: var(--accent);\">⚡ Smart</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</span><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if scope == "episodes" {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "Episodes scope — membership recomputes live from the tree below.")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Books scope — membership recomputes live from the tree below.")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</p></div></div></div><!-- The unified builder island: SAME component + vocabulary as\n\t\t\t\t the All-Books drawer (item 3.3's payload). Apply is\n\t\t\t\t collection-specific (writes the tree; the engine does the\n\t\t\t\t rest). The group-mode chips express ANY/ALL — the tree's\n\t\t\t\t root mode IS the collection's match semantics. --><div x-data=\"filterBuilderExtra\" data-filter-fields=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var9 string
|
var templ_7745c5c3_Var9 string
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(JSONArg(fieldsJSON))
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(collection.Name)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 54, Col: 45}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 36, Col: 90}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" data-scope=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h1><p class=\"flex items-center gap-2\" style=\"color: var(--text-secondary)\"><span class=\"badge text-xs\" style=\"background-color: var(--accent-muted); color: var(--accent);\">⚡ Smart</span> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if scope == "episodes" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Episodes scope — membership recomputes live from the tree below.")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "Books scope — membership recomputes live from the tree below.")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p></div></div></div><!-- The unified builder island: SAME component + vocabulary as\n\t\t\t\t the All-Books drawer (item 3.3's payload). Apply is\n\t\t\t\t collection-specific (writes the tree; the engine does the\n\t\t\t\t rest). The group-mode chips express ANY/ALL — the tree's\n\t\t\t\t root mode IS the collection's match semantics. --><div x-data=\"filterBuilderExtra\" data-filter-fields=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var10 string
|
var templ_7745c5c3_Var10 string
|
||||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(scope)
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(JSONArg(fieldsJSON))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 55, Col: 24}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 55, Col: 45}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-tree=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" data-scope=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var11 string
|
var templ_7745c5c3_Var11 string
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(JSONArg(treeJSON))
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(scope)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 56, Col: 34}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 56, Col: 24}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" class=\"card p-6\"><div class=\"flex items-center justify-between mb-3 px-1\"><h2 class=\"text-lg font-bold\" style=\"color: var(--text-primary)\">Filter tree</h2></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" data-tree=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 string
|
||||||
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(JSONArg(treeJSON))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/collection_rules.templ`, Line: 57, Col: 34}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" class=\"card p-6\"><div class=\"flex items-center justify-between mb-3 px-1\"><h2 class=\"text-lg font-bold\" style=\"color: var(--text-primary)\">Filter tree</h2></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -201,7 +214,7 @@ func CollectionRules(user User, collection CollectionData, fieldsJSON string, tr
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"mt-4 pt-4 border-t flex justify-end gap-2\" style=\"border-color: var(--border);\"><button type=\"button\" @click=\"backToCollection()\" class=\"btn btn-secondary\">Cancel</button> <button type=\"button\" @click=\"applyCollectionTree()\" class=\"btn btn-primary\">Apply to Collection</button></div></div></div></body></html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"mt-4 pt-4 border-t flex justify-end gap-2\" style=\"border-color: var(--border);\"><button type=\"button\" @click=\"backToCollection()\" class=\"btn btn-secondary\">Cancel</button> <button type=\"button\" @click=\"applyCollectionTree()\" class=\"btn btn-primary\">Apply to Collection</button></div></div></div></body></html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestRenderingPartialsRender(t *testing.T) {
|
|||||||
if err := CollectionBooksGrid(col, nil).Render(context.Background(), &b); err != nil {
|
if err := CollectionBooksGrid(col, nil).Render(context.Background(), &b); err != nil {
|
||||||
t.Fatalf("CollectionBooksGrid empty: %v", err)
|
t.Fatalf("CollectionBooksGrid empty: %v", err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(b.String(), "No books in this collection yet.") {
|
if !strings.Contains(b.String(), "Nothing in this collection yet.") {
|
||||||
t.Error("books grid empty state missing")
|
t.Error("books grid empty state missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ function initCollectionRules(id: string, _scope: string, _treeJSON: string): voi
|
|||||||
showToast("Not authenticated", "error");
|
showToast("Not authenticated", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const name = pageCollectionName();
|
||||||
|
if (!name) {
|
||||||
|
showToast("Cannot determine the collection name", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/collections/${collectionId}`, {
|
const response = await fetch(`/api/collections/${collectionId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
||||||
body: JSON.stringify({ name: pageCollectionName(), filter_tree: JSON.parse(wire) }),
|
body: JSON.stringify({ name, filter_tree: JSON.parse(wire) }),
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast("Smart collection updated", "success");
|
showToast("Smart collection updated", "success");
|
||||||
@@ -45,7 +50,9 @@ function initCollectionRules(id: string, _scope: string, _treeJSON: string): voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pageCollectionName(): string {
|
function pageCollectionName(): string {
|
||||||
return document.title.split(" - ")[0] || "Collection";
|
// The server renders the real name (the page title is presentation,
|
||||||
|
// and parsing it would RENAME the collection to "Smart Collection").
|
||||||
|
return document.body.dataset.collectionName || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function backToCollection(): void {
|
function backToCollection(): void {
|
||||||
|
|||||||
Reference in new issue
Block a user