Skip to content

Commit

Permalink
fix a bug where table sorting would break if table search was not als…
Browse files Browse the repository at this point in the history
…o enabled

fixes #660
  • Loading branch information
lovasoa committed Oct 31, 2024
1 parent 1600faa commit 6c94a5d
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# CHANGELOG.md

## 0.30.1 (2024-10-31)
- fix a bug where table sorting would break if table search was not also enabled.

## 0.30.0 (2024-10-30)

### 🤖 Easy APIs
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlpage"
version = "0.30.0"
version = "0.30.1"
edition = "2021"
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."
keywords = ["web", "sql", "framework"]
Expand Down
11 changes: 6 additions & 5 deletions examples/official-site/sqlpage/migrations/01_documentation.sql
Original file line number Diff line number Diff line change
Expand Up @@ -708,15 +708,15 @@ INSERT INTO example(component, description, properties) VALUES
'{"Forename": "Ophir", "Surname": "Lojkine", "Pseudonym": "lovasoa"},' ||
'{"Forename": "Linus", "Surname": "Torvalds", "Pseudonym": "torvalds"}]')),
('table', 'A table that uses markdown to display links',
json('[{"component":"table", "markdown": "Name", "icon": "icon", "sort": true, "search": true}, '||
json('[{"component":"table", "markdown": "Name", "icon": "icon", "search": true}, '||
'{"icon": "table", "name": "[Table](?component=table)", "description": "Displays SQL results as a searchable table.", "_sqlpage_color": "red"},
{"icon": "timeline", "name": "[Chart](?component=chart)", "description": "Show graphs based on numeric data."}
]')),
(
'table',
'A table with numbers',
'A table with column sorting. Sorting sorts numbers in numeric order, and strings in alphabetical order.',
json(
'[{"component":"table", "initial_search_value": "F-24", "sort": true, "align_right": ["Price ($)", "Amount in stock"]}, ' ||
'[{"component":"table", "sort": true, "align_right": ["Price ($)", "Amount in stock"]}, ' ||
'{"id": 31456, "part_no": "MIC-ROCC-F-23-206-C", "Price ($)": 12, "Amount in stock": 5},
{"id": 996, "part_no": "MIC-ROCC-F-24-206-A", "Price ($)": 1, "Amount in stock": 15},
{"id": 131456, "part_no": "KIB-ROCC-F-24-205-B", "Price ($)": 127, "Amount in stock": 9}
Expand All @@ -726,12 +726,13 @@ INSERT INTO example(component, description, properties) VALUES
'table',
'A table with some presentation options',
json(
'[{"component":"table", "hover": true, "striped_rows": true, "description": "Some Star Trek Starfleet starships", "small": true},'||
'[{"component":"table", "hover": true, "striped_rows": true, "description": "Some Star Trek Starfleet starships", "small": true, "initial_search_value": "NCC-" },'||
'{"name": "USS Enterprise", "registry": "NCC-1701-C", "class":"Ambassador"},
{"name": "USS Archer", "registry": "NCC-44278", "class":"Archer"},
{"name": "USS Endeavour", "registry": "NCC-06", "class":"Columbia"},
{"name": "USS Constellation", "registry": "NCC-1974", "class":"Constellation"},
{"name": "USS Dakota", "registry": "NCC-63892", "class":"Akira"}
{"name": "USS Dakota", "registry": "NCC-63892", "class":"Akira"},
{"name": "USS Defiant", "registry": "IX-74205", "class":"Defiant"}
]'
)),
(
Expand Down
8 changes: 5 additions & 3 deletions sqlpage/sqlpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function sqlpage_card() {

/** @param {HTMLElement} el */
function table_search_sort(el) {
/** @type {HTMLInputElement} */
/** @type {HTMLInputElement | null} */
const search_input = el.querySelector("input.search");
const sort_buttons = [...el.querySelectorAll("button.sort[data-sort]")];
const item_parent = el.querySelector("tbody");
Expand All @@ -42,8 +42,10 @@ function table_search_sort(el) {
item.el.style.display = show ? "" : "none";
});
}
search_input.addEventListener("input", onSearch);
onSearch();
if (search_input) {
search_input.addEventListener("input", onSearch);
onSearch();
}
sort_buttons.forEach((button, button_index) => {
button.addEventListener("click", function sort_items() {
const sort_desc = button.classList.contains("asc");
Expand Down
2 changes: 1 addition & 1 deletion sqlpage/templates/table.handlebars
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="card my-2 {{class}}" {{#if overflow}}style="width: fit-content;"{{/if}} {{#if id}}id="{{id}}"{{/if}}>
<div class="card-body">
<div class="table-responsive" {{#if (or sort search)}}data-pre-init="table"{{/if}}>
<div class="table-responsive" {{#if (or sort (or search initial_search_value))}}data-pre-init="table"{{/if}}>
{{#if (or search initial_search_value)}}
<div class="p-2">
<input type="search" class="form-control form-control-rounded fs-6 search" placeholder="Search…"
Expand Down
58 changes: 41 additions & 17 deletions tests/end-to-end/official-site.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,57 @@ test('Authentication example', async ({ page }) => {
await expect(page.getByText('You are logged in as admin')).toBeVisible();
});

test('table filtering and sorting', async ({ page }) => {
await page.goto(BASE + '/documentation.sql?component=table#component');
await expect(page.getByText('Loading...')).not.toBeVisible();

// Find the specific table section containing "Table" and "Chart"
test('table filtering', async ({ page }) => {
await page.goto(BASE + '/documentation.sql?component=table');
const tableSection = page.locator('.table-responsive', {
has: page.getByRole('cell', { name: 'Chart' })
});

// Test search filtering
const searchInput = tableSection.getByPlaceholder('Search…');
await searchInput.fill('chart');
await expect(tableSection.getByRole('cell', { name: 'Chart' })).toBeVisible();
await expect(tableSection.getByRole('cell', { name: 'Table' })).not.toBeVisible();
});

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

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

// Test reverse sorting
await tableSection.getByRole('button', { name: 'name' }).click();
names = await tableSection.locator('td.name').allInnerTexts();
const reverseSortedNames = [...names].sort().reverse();
expect(names).toEqual(reverseSortedNames);
await tableSection.getByRole('button', { name: 'id' }).click();
ids = await tableSection.locator('td.id').allInnerTexts();
numericIds = ids.map(id => parseInt(id));
const reverseSortedIds = [...numericIds].sort((a, b) => b - a);
expect(numericIds).toEqual(reverseSortedIds);

// Test amount in stock column sorting
await tableSection.getByRole('button', { name: 'Amount in stock' }).click();
let amounts = await tableSection.locator('td.Amount').allInnerTexts();
let numericAmounts = amounts.map(amount => parseInt(amount));
const sortedAmounts = [...numericAmounts].sort((a, b) => a - b);
expect(numericAmounts).toEqual(sortedAmounts);
});


test('no console errors on table page', async ({ page }) => {
const errors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});

await page.goto(BASE + '/documentation.sql?component=table');
await page.waitForLoadState('networkidle');

expect(errors).toHaveLength(0);
});

0 comments on commit 6c94a5d

Please sign in to comment.