From 8f38e5f280acedf51d470d6e7428289af5ca614f Mon Sep 17 00:00:00 2001 From: Lee Wannacott Date: Sat, 30 Mar 2024 21:23:01 +1300 Subject: [PATCH] =?UTF-8?q?Handle=20common=20currencies=20($=C2=A3?= =?UTF-8?q?=E2=82=AC=C2=A5)=20=20with=20numeric-sort=20class.=20=20=20(#13?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle common currencies and negatives. * Prettify files. * Add bitcoin and ethereum currency signs. * Update readme to say numeric-sort now supports currency --- README.md | 2 +- public/table-sort.js | 16 +-- test/table.test.js | 53 ++++++++ test/tagsInferenceTable.js | 7 +- test/tagsInferenceTable.test.js | 217 ++++++++++++++++---------------- 5 files changed, 173 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 0acc86a..f40867f 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Examples on using table-sort-js with frontend frameworks such as [React.js](http | <th> Inferred Classes. | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| "numeric-sort" | Sorts numbers including decimals - Positive, Negative (in both minus and parenthesis representations) | +| "numeric-sort" | Sorts numbers including decimals - Positive, Negative (in both minus and parenthesis representations). Supports for common currencies e.g ($£€¥) | | "dates-dmy-sort" | Sorts dates in dd/mm/yyyy format. e.g (18/10/1995). Can use "/" or "-" as separator. | | "dates-ymd-sort" | Sorts dates in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) yyyy/mm/dd format. e.g (2021/10/28). Use "/" or "-" as separator. | | "file-size-sort" | Sorts file sizes(B->TiB) uses the binary prefix. (e.g 10 B, 100 KiB, 1 MiB); optional space between number and prefix. | diff --git a/public/table-sort.js b/public/table-sort.js index efd1ec3..c44ba3a 100644 --- a/public/table-sort.js +++ b/public/table-sort.js @@ -64,9 +64,10 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) { // Don't infer dates with delimiter "."; as could capture semantic version numbers. const dmyRegex = /^(\d\d?)[/-](\d\d?)[/-]((\d\d)?\d\d)/; const ymdRegex = /^(\d\d\d\d)[/-](\d\d?)[/-](\d\d?)/; - // const numericRegex = /^(?:\(\d+(?:\.\d+)?\)|-?\d+(?:\.\d+)?)$/; doesn't handle commas const numericRegex = - /^-?(?:\d{1,3}(?:[',]\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?(?:[',]\d{3})*?)$/; + /^-?(?:[$£€¥₩₽₺₣฿₿Ξξ¤¿\u20A1\uFFE0]\d{1,3}(?:[',]\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?(?:[',]\d{3})*?)$/; + + const inferableClasses = { runtime: { regexp: runtimeRegex, class: "runtime-sort", count: 0 }, filesize: { regexp: fileSizeRegex, class: "file-size-sort", count: 0 }, @@ -342,7 +343,7 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) { function parseNumberFromString(str) { let num; str = str.slice(0, str.indexOf("#")); - if (str.match(/^\((\d+(?:\.\d+)?)\)$/)) { + if (str.match(/^\(-?(\d+(?:\.\d+)?)\)$/)) { num = -1 * Number(str.slice(1, -1)); } else { num = Number(str); @@ -359,11 +360,10 @@ function tableSortJs(testingTableSortJS = false, domDocumentWindow = document) { } function handleNumbers(str1, str2) { - let num1, num2; - str1 = str1.replaceAll(",", ""); - str2 = str2.replaceAll(",", ""); - num1 = parseNumberFromString(str1); - num2 = parseNumberFromString(str2); + const currencyAndComma = /[$£€¥₩₽₺₣฿₿Ξξ¤¿\u20A1\uFFE0, ]/g + str1 = str1.replace(currencyAndComma, ""); + str2 = str2.replace(currencyAndComma, ""); + const [num1, num2] = [parseNumberFromString(str1),parseNumberFromString(str2)]; if (!isNaN(num1) && !isNaN(num2)) { return num1 - num2; diff --git a/test/table.test.js b/test/table.test.js index 8a5a687..6eb460c 100644 --- a/test/table.test.js +++ b/test/table.test.js @@ -617,6 +617,59 @@ test("Sort all combination of negative and positive integers and decimal numbers }); }); +test("numeric-sort - various currency", () => { + expect( + createTestTable( + { + col0: { + td: [ + "-$4.01", + "-¥2.02", + "($5.03)", + "$4.64", + "-£29,675", + "-$5.21", + "-£50,854", + "£2,038,720", + "£283,838,720", + "-£481,177", + "$2.01", + "$2.11", + "฿2.21", + "-£1,976,799", + "£2,265", + "(£420,252)", + "-€2,409,060", + "-£755,905", + ], + }, + }, + { classTags: "numeric-sort" } + ) + ).toStrictEqual({ + col0: [ + "-€2,409,060", + "-£1,976,799", + "-£755,905", + "-£481,177", + "(£420,252)", + "-£50,854", + "-£29,675", + "-$5.21", + "($5.03)", + "-$4.01", + "-¥2.02", + "$2.01", + "$2.11", + "฿2.21", + "$4.64", + "£2,265", + "£2,038,720", + "£283,838,720", + ], + }); +}); + test("default behavior without cells-sort (tr's move when sorted)", () => { expect( createTestTable( diff --git a/test/tagsInferenceTable.js b/test/tagsInferenceTable.js index f096992..e747441 100644 --- a/test/tagsInferenceTable.js +++ b/test/tagsInferenceTable.js @@ -76,9 +76,10 @@ function createTestTable( tableSortJs(true, dom.window.document); // Make an array from table contents to test if sorted correctly. let table = dom.window.document.querySelector("table"); - const tableHeadWithInferredClassName = table - .querySelectorAll("thead th") - let inferedClassNamesOfTh = Array.from(tableHeadWithInferredClassName).map((e)=>e.getAttribute("class")) + const tableHeadWithInferredClassName = table.querySelectorAll("thead th"); + let inferedClassNamesOfTh = Array.from(tableHeadWithInferredClassName).map( + (e) => e.getAttribute("class") + ); return inferedClassNamesOfTh; } diff --git a/test/tagsInferenceTable.test.js b/test/tagsInferenceTable.test.js index 90716df..2cec693 100644 --- a/test/tagsInferenceTable.test.js +++ b/test/tagsInferenceTable.test.js @@ -1,41 +1,39 @@ const createTestTable = require("./tagsInferenceTable"); - test("InferSortClassesOnTH - FILE SIZE", () => { expect( createTestTable({ - col0: { - td: [ - "10MB", - "10GB", - "10TB", - "10B", - "10MiB", - "10TiB", - "10Kib", - "10KB", - "10GiB", - ], - }, - // add in space and make some undercase - col1: { - td: [ - "10 mB", - "10 GB", - "10 Tb", - "10 B", - "10 mib", - "10 tib", - "10 kib", - "10 kB", - "10 giB", - ], - }, + col0: { + td: [ + "10MB", + "10GB", + "10TB", + "10B", + "10MiB", + "10TiB", + "10Kib", + "10KB", + "10GiB", + ], + }, + // add in space and make some undercase + col1: { + td: [ + "10 mB", + "10 GB", + "10 Tb", + "10 B", + "10 mib", + "10 tib", + "10 kib", + "10 kB", + "10 giB", + ], + }, }) ).toStrictEqual(["file-size-sort", "file-size-sort"]); }); - test("InferSortClassesOnTH - DATES", () => { expect( createTestTable({ @@ -67,29 +65,27 @@ test("InferSortClassesOnTH - DATES", () => { }, }) // two dates-dmy-sort as mdy is not an inferred class but explicit override. - ).toStrictEqual(["dates-ymd-sort","dates-dmy-sort","dates-dmy-sort"]); + ).toStrictEqual(["dates-ymd-sort", "dates-dmy-sort", "dates-dmy-sort"]); }); - - test("InferSortClassesOnTH - RUNTIME", () => { expect( createTestTable({ - col0: { - td: [ - "2m 52s", - "1h 20m 10s", - "3s", - "11h 10m 10s", - "7s", - "11m 40s", - "36s", - "1h 10m 10s", - "9m 44s", - "1m 36s", - "41s", - ], - }, + col0: { + td: [ + "2m 52s", + "1h 20m 10s", + "3s", + "11h 10m 10s", + "7s", + "11m 40s", + "36s", + "1h 10m 10s", + "9m 44s", + "1m 36s", + "41s", + ], + }, }) ).toStrictEqual(["runtime-sort"]); }); @@ -97,68 +93,69 @@ test("InferSortClassesOnTH - RUNTIME", () => { test("InferSortClassesOnTH - NUMERIC", () => { expect( createTestTable({ - // commas - col0: { - td: [ - "20,000.89", - "30,000.32", - "1", - "0.111", - "21,000.92", - "19845", - "12000", - "-90", - "-10,000.39", - "-10,000.10", - ], - }, - // negative numbers - col1: { td: ["1.05", "-2.3", "-3", "1", "-6", "(1.4)", "14"] }, - // decimals - col2: { td: ["0.1", "0.2", "0.3", "0.11", "0.13", "0.13", "0.14"] }, - col3: { - td: [ - "1.05", - "-2.3", - "-3", - "1", - "-6", - "", - "(0.5)", - "1a", - "b", - "(c)", - "{1}", - ], - }, - // TODO HANDLE CURRENCY $ / pounds, etc.... + // commas + col0: { + td: [ + "20,000.89", + "30,000.32", + "1", + "0.111", + "21,000.92", + "19845", + "12000", + "-90", + "-10,000.39", + "-10,000.10", + ], + }, + // negative numbers + col1: { td: ["1.05", "-2.3", "-3", "1", "-6", "(1.4)", "14"] }, + // decimals + col2: { td: ["0.1", "0.2", "0.3", "0.11", "0.13", "0.13", "0.14"] }, + col3: { + td: [ + "1.05", + "-2.3", + "-3", + "1", + "-6", + "", + "(0.5)", + "1a", + "b", + "(c)", + "{1}", + ], + }, + // TODO HANDLE CURRENCY $ / pounds, etc.... + col4: { + td: [ + "-$4.01", + "-¥2.02", + "-$5.03", + "$4.64", + "-£29,675", + "-$5.21", + "-£50,854", + "£2,038,720", + "£283,838,720", + "-£481,177", + "$2.01", + "$2.11", + "$2.21", + "-£1,976,799", + "£2,265", + "-£420,252", + "-€2,409,060", + "-£755,905", + ], + }, }) - ).toStrictEqual(["numeric-sort","numeric-sort","numeric-sort","numeric-sort"]); + ).toStrictEqual([ + "numeric-sort", + "numeric-sort", + "numeric-sort", + "numeric-sort", + "numeric-sort", + ]); }); - - -// TODO no-class-infer -// test("InferSortClassesOnTH - no-class-infer", () => { -// expect( -// createTestTable( -// { -// col0: { -// td: [ -// "2m 52s", -// "1h 20m 10s", -// "3s", -// "11h 10m 10s", -// "7s", -// "11m 40s", -// "36s", -// "1h 10m 10s", -// "9m 44s", -// "1m 36s", -// "41s", -// ], -// }, -// }, -// // props={ tableTags: "no-class-infer"}, -// ) -// ).toStrictEqual(["runtime-sort"]); -// });