Skip to content

Commit 6c94a5d

Browse files
committed
fix a bug where table sorting would break if table search was not also enabled
fixes #660
1 parent 1600faa commit 6c94a5d

File tree

7 files changed

+58
-28
lines changed

7 files changed

+58
-28
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG.md
22

3+
## 0.30.1 (2024-10-31)
4+
- fix a bug where table sorting would break if table search was not also enabled.
5+
36
## 0.30.0 (2024-10-30)
47

58
### 🤖 Easy APIs

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlpage"
3-
version = "0.30.0"
3+
version = "0.30.1"
44
edition = "2021"
55
description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components."
66
keywords = ["web", "sql", "framework"]

examples/official-site/sqlpage/migrations/01_documentation.sql

+6-5
Original file line numberDiff line numberDiff line change
@@ -708,15 +708,15 @@ INSERT INTO example(component, description, properties) VALUES
708708
'{"Forename": "Ophir", "Surname": "Lojkine", "Pseudonym": "lovasoa"},' ||
709709
'{"Forename": "Linus", "Surname": "Torvalds", "Pseudonym": "torvalds"}]')),
710710
('table', 'A table that uses markdown to display links',
711-
json('[{"component":"table", "markdown": "Name", "icon": "icon", "sort": true, "search": true}, '||
711+
json('[{"component":"table", "markdown": "Name", "icon": "icon", "search": true}, '||
712712
'{"icon": "table", "name": "[Table](?component=table)", "description": "Displays SQL results as a searchable table.", "_sqlpage_color": "red"},
713713
{"icon": "timeline", "name": "[Chart](?component=chart)", "description": "Show graphs based on numeric data."}
714714
]')),
715715
(
716716
'table',
717-
'A table with numbers',
717+
'A table with column sorting. Sorting sorts numbers in numeric order, and strings in alphabetical order.',
718718
json(
719-
'[{"component":"table", "initial_search_value": "F-24", "sort": true, "align_right": ["Price ($)", "Amount in stock"]}, ' ||
719+
'[{"component":"table", "sort": true, "align_right": ["Price ($)", "Amount in stock"]}, ' ||
720720
'{"id": 31456, "part_no": "MIC-ROCC-F-23-206-C", "Price ($)": 12, "Amount in stock": 5},
721721
{"id": 996, "part_no": "MIC-ROCC-F-24-206-A", "Price ($)": 1, "Amount in stock": 15},
722722
{"id": 131456, "part_no": "KIB-ROCC-F-24-205-B", "Price ($)": 127, "Amount in stock": 9}
@@ -726,12 +726,13 @@ INSERT INTO example(component, description, properties) VALUES
726726
'table',
727727
'A table with some presentation options',
728728
json(
729-
'[{"component":"table", "hover": true, "striped_rows": true, "description": "Some Star Trek Starfleet starships", "small": true},'||
729+
'[{"component":"table", "hover": true, "striped_rows": true, "description": "Some Star Trek Starfleet starships", "small": true, "initial_search_value": "NCC-" },'||
730730
'{"name": "USS Enterprise", "registry": "NCC-1701-C", "class":"Ambassador"},
731731
{"name": "USS Archer", "registry": "NCC-44278", "class":"Archer"},
732732
{"name": "USS Endeavour", "registry": "NCC-06", "class":"Columbia"},
733733
{"name": "USS Constellation", "registry": "NCC-1974", "class":"Constellation"},
734-
{"name": "USS Dakota", "registry": "NCC-63892", "class":"Akira"}
734+
{"name": "USS Dakota", "registry": "NCC-63892", "class":"Akira"},
735+
{"name": "USS Defiant", "registry": "IX-74205", "class":"Defiant"}
735736
]'
736737
)),
737738
(

sqlpage/sqlpage.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function sqlpage_card() {
2424

2525
/** @param {HTMLElement} el */
2626
function table_search_sort(el) {
27-
/** @type {HTMLInputElement} */
27+
/** @type {HTMLInputElement | null} */
2828
const search_input = el.querySelector("input.search");
2929
const sort_buttons = [...el.querySelectorAll("button.sort[data-sort]")];
3030
const item_parent = el.querySelector("tbody");
@@ -42,8 +42,10 @@ function table_search_sort(el) {
4242
item.el.style.display = show ? "" : "none";
4343
});
4444
}
45-
search_input.addEventListener("input", onSearch);
46-
onSearch();
45+
if (search_input) {
46+
search_input.addEventListener("input", onSearch);
47+
onSearch();
48+
}
4749
sort_buttons.forEach((button, button_index) => {
4850
button.addEventListener("click", function sort_items() {
4951
const sort_desc = button.classList.contains("asc");

sqlpage/templates/table.handlebars

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="card my-2 {{class}}" {{#if overflow}}style="width: fit-content;"{{/if}} {{#if id}}id="{{id}}"{{/if}}>
22
<div class="card-body">
3-
<div class="table-responsive" {{#if (or sort search)}}data-pre-init="table"{{/if}}>
3+
<div class="table-responsive" {{#if (or sort (or search initial_search_value))}}data-pre-init="table"{{/if}}>
44
{{#if (or search initial_search_value)}}
55
<div class="p-2">
66
<input type="search" class="form-control form-control-rounded fs-6 search" placeholder="Search…"

tests/end-to-end/official-site.spec.ts

+41-17
Original file line numberDiff line numberDiff line change
@@ -78,33 +78,57 @@ test('Authentication example', async ({ page }) => {
7878
await expect(page.getByText('You are logged in as admin')).toBeVisible();
7979
});
8080

81-
test('table filtering and sorting', async ({ page }) => {
82-
await page.goto(BASE + '/documentation.sql?component=table#component');
83-
await expect(page.getByText('Loading...')).not.toBeVisible();
84-
85-
// Find the specific table section containing "Table" and "Chart"
81+
test('table filtering', async ({ page }) => {
82+
await page.goto(BASE + '/documentation.sql?component=table');
8683
const tableSection = page.locator('.table-responsive', {
8784
has: page.getByRole('cell', { name: 'Chart' })
8885
});
8986

90-
// Test search filtering
9187
const searchInput = tableSection.getByPlaceholder('Search…');
9288
await searchInput.fill('chart');
9389
await expect(tableSection.getByRole('cell', { name: 'Chart' })).toBeVisible();
9490
await expect(tableSection.getByRole('cell', { name: 'Table' })).not.toBeVisible();
91+
});
9592

96-
// Clear search
97-
await searchInput.clear();
93+
test('table sorting', async ({ page }) => {
94+
await page.goto(BASE + '/documentation.sql?component=table');
95+
const tableSection = page.locator('.table-responsive', {
96+
has: page.getByRole('cell', { name: '31456' })
97+
});
9898

99-
// Test sorting by name
100-
await tableSection.getByRole('button', { name: 'name' }).click();
101-
let names = await tableSection.locator('td.name').allInnerTexts();
102-
const sortedNames = [...names].sort();
103-
expect(names).toEqual(sortedNames);
99+
// Test numeric sorting on id column
100+
await tableSection.getByRole('button', { name: 'id' }).click();
101+
let ids = await tableSection.locator('td.id').allInnerTexts();
102+
let numericIds = ids.map(id => parseInt(id));
103+
const sortedIds = [...numericIds].sort((a, b) => a - b);
104+
expect(numericIds).toEqual(sortedIds);
104105

105106
// Test reverse sorting
106-
await tableSection.getByRole('button', { name: 'name' }).click();
107-
names = await tableSection.locator('td.name').allInnerTexts();
108-
const reverseSortedNames = [...names].sort().reverse();
109-
expect(names).toEqual(reverseSortedNames);
107+
await tableSection.getByRole('button', { name: 'id' }).click();
108+
ids = await tableSection.locator('td.id').allInnerTexts();
109+
numericIds = ids.map(id => parseInt(id));
110+
const reverseSortedIds = [...numericIds].sort((a, b) => b - a);
111+
expect(numericIds).toEqual(reverseSortedIds);
112+
113+
// Test amount in stock column sorting
114+
await tableSection.getByRole('button', { name: 'Amount in stock' }).click();
115+
let amounts = await tableSection.locator('td.Amount').allInnerTexts();
116+
let numericAmounts = amounts.map(amount => parseInt(amount));
117+
const sortedAmounts = [...numericAmounts].sort((a, b) => a - b);
118+
expect(numericAmounts).toEqual(sortedAmounts);
119+
});
120+
121+
122+
test('no console errors on table page', async ({ page }) => {
123+
const errors: string[] = [];
124+
page.on('console', msg => {
125+
if (msg.type() === 'error') {
126+
errors.push(msg.text());
127+
}
128+
});
129+
130+
await page.goto(BASE + '/documentation.sql?component=table');
131+
await page.waitForLoadState('networkidle');
132+
133+
expect(errors).toHaveLength(0);
110134
});

0 commit comments

Comments
 (0)