Fix: issue 459 / sort direct nodes
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { Table } from "@components/generic/Table/index.tsx";
|
||||
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
|
||||
import { Mono } from "@components/generic/Mono.tsx";
|
||||
|
||||
|
||||
describe("Generic Table", () => {
|
||||
it("Can render an empty table.", () => {
|
||||
render(
|
||||
<Table
|
||||
headings={[]}
|
||||
rows={[]}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("table")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Can render a table with headers and no rows.", async () => {
|
||||
render(
|
||||
<Table
|
||||
headings={[
|
||||
{ title: "", type: "blank", sortable: false },
|
||||
{ title: "Short Name", type: "normal", sortable: true },
|
||||
{ title: "Long Name", type: "normal", sortable: true },
|
||||
{ title: "Model", type: "normal", sortable: true },
|
||||
{ title: "MAC Address", type: "normal", sortable: true },
|
||||
{ title: "Last Heard", type: "normal", sortable: true },
|
||||
{ title: "SNR", type: "normal", sortable: true },
|
||||
{ title: "Encryption", type: "normal", sortable: false },
|
||||
{ title: "Connection", type: "normal", sortable: true },
|
||||
]}
|
||||
rows={[]}
|
||||
/>
|
||||
);
|
||||
await screen.findByRole('table');
|
||||
expect(screen.getAllByRole("columnheader")).toHaveLength(9);
|
||||
});
|
||||
|
||||
// A simplified version of the rows in pages/Nodes.tsx for testing purposes
|
||||
const mockDevicesWithShortNameAndConnection = [
|
||||
{user: {shortName: "TST1"}, hopsAway: 1, lastHeard: Date.now() + 1000 },
|
||||
{user: {shortName: "TST2"}, hopsAway: 0, lastHeard: Date.now() + 4000 },
|
||||
{user: {shortName: "TST3"}, hopsAway: 4, lastHeard: Date.now() },
|
||||
{user: {shortName: "TST4"}, hopsAway: 3, lastHeard: Date.now() + 2000 }
|
||||
];
|
||||
|
||||
const mockRows = mockDevicesWithShortNameAndConnection.map(node => [
|
||||
<h1 data-testshortname> { node.user.shortName } </h1>,
|
||||
<><TimeAgo timestamp={node.lastHeard * 1000} /></>,
|
||||
<Mono key="hops" data-testhops>
|
||||
{node.lastHeard !== 0
|
||||
? node.hopsAway === 0
|
||||
? "Direct"
|
||||
: `${node.hopsAway?.toString()} ${
|
||||
node.hopsAway > 1 ? "hops" : "hop"
|
||||
} away`
|
||||
: "-"}
|
||||
</Mono>
|
||||
])
|
||||
|
||||
it("Can sort rows appropriately.", async () => {
|
||||
render(
|
||||
<Table
|
||||
headings={[
|
||||
{ title: "Short Name", type: "normal", sortable: true },
|
||||
{ title: "Last Heard", type: "normal", sortable: true },
|
||||
{ title: "Connection", type: "normal", sortable: true },
|
||||
]}
|
||||
rows={mockRows}
|
||||
/>
|
||||
);
|
||||
const renderedTable = await screen.findByRole('table');
|
||||
const columnHeaders = screen.getAllByRole("columnheader");
|
||||
expect(columnHeaders).toHaveLength(3);
|
||||
|
||||
// Will be sorted "Last heard" "asc" by default
|
||||
expect( [...renderedTable.querySelectorAll('[data-testshortname]')]
|
||||
.map(el=>el.textContent)
|
||||
.map(v=>v?.trim())
|
||||
.join(','))
|
||||
.toMatch('TST2,TST4,TST1,TST3');
|
||||
|
||||
fireEvent.click(columnHeaders[0]);
|
||||
|
||||
// Re-sort by Short Name asc
|
||||
expect( [...renderedTable.querySelectorAll('[data-testshortname]')]
|
||||
.map(el=>el.textContent)
|
||||
.map(v=>v?.trim())
|
||||
.join(','))
|
||||
.toMatch('TST1,TST2,TST3,TST4');
|
||||
|
||||
fireEvent.click(columnHeaders[0]);
|
||||
|
||||
// Re-sort by Short Name desc
|
||||
expect( [...renderedTable.querySelectorAll('[data-testshortname]')]
|
||||
.map(el=>el.textContent)
|
||||
.map(v=>v?.trim())
|
||||
.join(','))
|
||||
.toMatch('TST4,TST3,TST2,TST1');
|
||||
|
||||
fireEvent.click(columnHeaders[2]);
|
||||
|
||||
// Re-sort by Hops Away
|
||||
expect( [...renderedTable.querySelectorAll('[data-testshortname]')]
|
||||
.map(el=>el.textContent)
|
||||
.map(v=>v?.trim())
|
||||
.join(','))
|
||||
.toMatch('TST2,TST1,TST4,TST3');
|
||||
});
|
||||
})
|
||||
@@ -12,6 +12,20 @@ export interface Heading {
|
||||
sortable: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hopsAway String describing the number of hops away the node is from the current node
|
||||
* @returns number of hopsAway or `0` if hopsAway is 'Direct'
|
||||
*/
|
||||
function numericHops(hopsAway: string): number {
|
||||
if(hopsAway.match(/direct/i)){
|
||||
return 0;
|
||||
}
|
||||
if ( hopsAway.match(/\d+\s+hop/gi) ) {
|
||||
return Number( hopsAway.match(/(\d+)\s+hop/i)?.[1] );
|
||||
}
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
export const Table = ({ headings, rows }: TableProps) => {
|
||||
const [sortColumn, setSortColumn] = useState<string | null>("Last Heard");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
|
||||
@@ -46,6 +60,20 @@ export const Table = ({ headings, rows }: TableProps) => {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Custom comparison for 'Connection' column
|
||||
if (sortColumn === "Connection") {
|
||||
const aNumHops = numericHops(aValue instanceof Array ? aValue[0] : aValue);
|
||||
const bNumHops = numericHops(bValue instanceof Array ? bValue[0] : bValue);
|
||||
|
||||
if (aNumHops < bNumHops) {
|
||||
return sortOrder === "asc" ? -1 : 1;
|
||||
}
|
||||
if (aNumHops > bNumHops) {
|
||||
return sortOrder === "asc" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Default comparison for other columns
|
||||
if (aValue < bValue) {
|
||||
return sortOrder === "asc" ? -1 : 1;
|
||||
@@ -100,4 +128,4 @@ export const Table = ({ headings, rows }: TableProps) => {
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user