From fa07ba8910dd2c4bab97a9e61dda6b526ac98aa3 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Wed, 30 Sep 2026 17:13:27 -0300 Subject: [PATCH] fix: normalize nutrition units and retain zero-valued nutrients --- README.md | 12 +- app/produto/[barcode]/page.tsx | 11 +- lib/nutrition.ts | 24 ++++ lib/openFoodFactsApi.ts | 35 +++-- package.json | 5 +- pnpm-lock.yaml | 256 +++++++++++++++++---------------- tests/nutrition.test.ts | 58 ++++++++ tsconfig.json | 1 + 8 files changed, 253 insertions(+), 149 deletions(-) create mode 100644 lib/nutrition.ts create mode 100644 tests/nutrition.test.ts diff --git a/README.md b/README.md index 34e544d..86eb376 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ nixpacks.toml, dokploy.yaml configuração de deploy ## Verificação -Não há testes automatizados neste repositório (nenhum framework de teste nas dependências) e não há CI configurada. O que existe hoje: +Há testes offline com o runner nativo do Node.js (sem framework adicional) e não há CI configurada. O que existe hoje: - `pnpm lint` roda o ESLint (via `next lint`); - `pnpm build` roda `prisma generate` e o `next build` com checagem de tipos; @@ -226,7 +226,7 @@ Medições feitas nesta auditoria, em 14/09/2026, com Node 24.20.0 e pnpm 12.4.1 ## Estado atual e limitações - A busca textual depende do endpoint legado `cgi/search.pl` do Open Food Facts, que respondeu HTTP 503 durante a verificação. Na prática, buscar por nome retorna lista vazia, com a rota respondendo 200 e `count: 0`, sem derrubar a aplicação. A consulta por código de barras continua funcionando pelo endpoint `api/v2/product`. -- Sem testes automatizados e sem CI. +- Testes offline de normalização e ordenação nutricional; ainda sem CI. - O painel de filtros avançados da página de resultados guarda as escolhas na chave do cache, mas a busca enviada para `/api/search` leva apenas texto, país e paginação. Hoje o que realmente filtra a lista são os atalhos de classificação Nutri-Score e o botão de produtos sem alérgenos. - A ordenação implementada em `lib/openFoodFactsApi.ts` cobre quatro critérios (relevância, Nutri-Score, energia e nome). As demais opções do seletor caem no caso padrão da função e não reordenam o resultado. - O servidor grava em `data/products-cache.json`, que é versionado. Usar a aplicação altera o conteúdo do arquivo no diretório de trabalho. @@ -251,3 +251,11 @@ Medições feitas nesta auditoria, em 14/09/2026, com Node 24.20.0 e pnpm 12.4.1 Licença de uso pessoal, definida em [`LICENSE`](LICENSE): é permitido usar em máquina local, estudar o código e rodar para desenvolvimento. Não é permitido deploy público, uso comercial, redistribuição nem obras derivadas para distribuição. A instância em `nutriscan.evandro.dev.br` é do próprio autor. Autor: Evandro, e [@evandrodevbr](https://github.com/evandrodevbr). + +## Validação de dados nutricionais (2026-09-30) + +A exibição e a ordenação de energia priorizam `energy-kcal_100g`; na ausência dele, convertem o campo em kJ para kcal usando `1 kcal = 4,184 kJ`. A página do produto usa a mesma normalização. Zero é preservado como dado válido, e valores ausentes, negativos ou não finitos não entram no cálculo. Produtos sem energia ficam no fim da ordenação nas duas direções. A linha baseada em `salt_100g` é identificada como sal. + +Os exemplos de campos vêm da [documentação oficial do Open Food Facts](https://openfoodfacts.github.io/openfoodfacts-server/api/tutorial-off-api/). Valores por 100 g e referências existentes da interface são mantidos; o Nutri-Score continua vindo da API. + +Com Node.js 22.7+ (verificado em 24.19.0), `pnpm test` executa 6 regressões offline usando o runner nativo, sem adicionar um framework. `pnpm lint`, `pnpm exec tsc --noEmit` e `pnpm build` completam a validação. O build foi verificado com SQLite temporário, sem conexão com um banco de produção. Next.js e eslint-config-next foram atualizados para 15.5.24. diff --git a/app/produto/[barcode]/page.tsx b/app/produto/[barcode]/page.tsx index 836ea94..62463ee 100644 --- a/app/produto/[barcode]/page.tsx +++ b/app/produto/[barcode]/page.tsx @@ -12,7 +12,8 @@ import { Share2, } from "lucide-react"; import Image from "next/image"; -import { searchByBarcode, Product, formatNutritionData } from "@/lib/openFoodFactsApi"; +import { searchByBarcode, Product } from "@/lib/openFoodFactsApi"; +import { getEnergyKcal, getNutrientValue } from "@/lib/nutrition"; import { ProductSkeleton } from "@/app/components/ProductSkeleton"; /* ── Nutri-Score letter circle ─────────────────────────────────────────── */ @@ -305,7 +306,7 @@ export default function ProdutoPage() { /* ── Nutrition bars — % of daily reference ─────────────────────────── */ const dailyRef: Record = { - energy_100g: 2000, + "energy-kcal_100g": 2000, fat_100g: 70, saturated_fat_100g: 20, sugars_100g: 90, @@ -315,11 +316,11 @@ export default function ProdutoPage() { }; const nutriments = product.nutriments || {}; const nutritionRows = [ - { key: "energy_100g", label: "Energia", unit: "kcal" }, + { key: "energy-kcal_100g", label: "Energia", unit: "kcal" }, { key: "fat_100g", label: "Gorduras totais", unit: "g" }, { key: "saturated_fat_100g",label: "Gorduras saturadas", unit: "g" }, { key: "sugars_100g", label: "Açúcares", unit: "g" }, - { key: "salt_100g", label: "Sódio", unit: "g" }, + { key: "salt_100g", label: "Sal", unit: "g" }, { key: "fiber_100g", label: "Fibras", unit: "g" }, { key: "proteins_100g", label: "Proteínas", unit: "g" }, ]; @@ -701,7 +702,7 @@ export default function ProdutoPage() { > {nutritionRows.map((row) => { const raw = nutriments[row.key as keyof typeof nutriments]; - const v = raw != null ? Number(raw) : null; + const v = row.key === "energy-kcal_100g" ? getEnergyKcal(nutriments) : getNutrientValue(raw); if (v == null) return null; const ref = dailyRef[row.key] || 100; const pct = Math.min(100, Math.round((v / ref) * 100)); diff --git a/lib/nutrition.ts b/lib/nutrition.ts new file mode 100644 index 0000000..76d16b2 --- /dev/null +++ b/lib/nutrition.ts @@ -0,0 +1,24 @@ +/** Normalização dos valores nutricionais recebidos do Open Food Facts. */ +export function getNutrientValue(value: unknown): number | null { + if (typeof value !== "number" && typeof value !== "string") return null; + if (typeof value === "string" && value.trim() === "") return null; + const number = Number(value); + return Number.isFinite(number) && number >= 0 ? number : null; +} + +/** + * Prefere o campo explícito em kcal. Os campos energy/energy-kj usam kJ; + * 1 kcal = 4.184 kJ. Zero é um valor válido, distinto de dado ausente. + * https://openfoodfacts.github.io/openfoodfacts-server/api/tutorial-off-api/ + */ +export function getEnergyKcal(nutriments?: { + "energy-kcal_100g"?: unknown; + "energy-kj_100g"?: unknown; + energy_100g?: unknown; +}): number | null { + const kcal = getNutrientValue(nutriments?.["energy-kcal_100g"]); + if (kcal !== null) return kcal; + const kj = getNutrientValue(nutriments?.["energy-kj_100g"]) + ?? getNutrientValue(nutriments?.energy_100g); + return kj === null ? null : kj / 4.184; +} diff --git a/lib/openFoodFactsApi.ts b/lib/openFoodFactsApi.ts index 73a57e7..66787cb 100644 --- a/lib/openFoodFactsApi.ts +++ b/lib/openFoodFactsApi.ts @@ -3,6 +3,8 @@ * Melhorias: Memoização de Scores, Rate Limiting Robusto e Tipagem Estrita. */ +import { getEnergyKcal, getNutrientValue } from "./nutrition.ts"; + export interface Product { code: string; product_name: string; @@ -11,6 +13,8 @@ export interface Product { ingredients_text?: string; nutriments?: { energy_100g?: number; + "energy-kcal_100g"?: number; + "energy-kj_100g"?: number; fat_100g?: number; carbohydrates_100g?: number; proteins_100g?: number; @@ -185,6 +189,7 @@ export function sortProducts( value: p, score: calculateCompletenessScore(p), nutritionValue: getGradeValue(p.nutrition_grades), + energyKcal: getEnergyKcal(p.nutriments), })); // 2. Sort: Ordenação sobre valores em memória (O(n log n)) @@ -199,7 +204,9 @@ export function sortProducts( diff = a.nutritionValue - b.nutritionValue; break; case "energy": - diff = (a.value.nutriments?.energy_100g || 9999) - (b.value.nutriments?.energy_100g || 9999); + if (a.energyKcal === null) return b.energyKcal === null ? 0 : 1; + if (b.energyKcal === null) return -1; + diff = a.energyKcal - b.energyKcal; break; case "name": diff = (a.value.product_name || "").localeCompare(b.value.product_name || ""); @@ -240,21 +247,21 @@ export function formatNutritionData(p: Product) { const n = p.nutriments; if (!n) return null; - const fmt = (v: unknown, d: number = 1) => { - const num = - typeof v === "number" - ? v - : parseFloat(typeof v === "string" ? v : String(v)); - return isNaN(num) ? "N/A" : num.toFixed(d); + const fmt = (v: unknown, unit: string, d: number = 1) => { + const num = getNutrientValue(v); + return num === null ? "N/A" : `${num.toFixed(d)}${unit}`; }; + const energy = getEnergyKcal(n); return { - energy: n.energy_100g ? `${Math.round(n.energy_100g)} kcal` : "N/A", - fat: n.fat_100g ? `${fmt(n.fat_100g)}g` : "N/A", - carbs: n.carbohydrates_100g ? `${fmt(n.carbohydrates_100g)}g` : "N/A", - proteins: n.proteins_100g ? `${fmt(n.proteins_100g)}g` : "N/A", - sugars: n.sugars_100g ? `${fmt(n.sugars_100g)}g` : "N/A", - sodium: n.sodium_100g ? `${fmt(n.sodium_100g, 2)}g` : "N/A", + energy: energy === null ? "N/A" : `${Math.round(energy)} kcal`, + fat: fmt(n.fat_100g, "g"), + carbs: fmt(n.carbohydrates_100g, "g"), + carbohydrates: fmt(n.carbohydrates_100g, "g"), + proteins: fmt(n.proteins_100g, "g"), + sugars: fmt(n.sugars_100g, "g"), + fiber: fmt(n.fiber_100g, "g"), + sodium: fmt(n.sodium_100g, "g", 2), }; } @@ -269,4 +276,4 @@ export function detectSearchType(input: string): "barcode" | "name" { export function isValidBarcode(barcode: string): boolean { const cleaned = barcode.replace(/\D/g, ""); return cleaned.length >= 8 && cleaned.length <= 13 && /^\d+$/.test(cleaned); -} \ No newline at end of file +} diff --git a/package.json b/package.json index 4d8f793..4f1be21 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { + "test": "node --experimental-transform-types --test tests/*.test.ts", "dev": "next dev", "lint": "next lint", "postinstall": "prisma generate", @@ -30,7 +31,7 @@ "clsx": "^2.1.1", "framer-motion": "^11.18.2", "lucide-react": "^0.460.0", - "next": "^15.5.10", + "next": "^15.5.24", "next-themes": "^0.4.6", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -43,7 +44,7 @@ "@types/react-dom": "^19.0.0", "depcheck": "^1.4.7", "eslint": "^8.57.1", - "eslint-config-next": "^15.5.10", + "eslint-config-next": "^15.5.24", "postcss": "^8.5.10", "prisma": "^6.19.3", "tailwindcss": "^3.4.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cce0994..32d4e7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,8 +60,8 @@ importers: specifier: ^0.460.0 version: 0.460.0(react@19.2.7) next: - specifier: ^15.5.10 - version: 15.5.20(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: ^15.5.24 + version: 15.5.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -89,13 +89,13 @@ importers: version: 19.2.3(@types/react@19.2.17) depcheck: specifier: ^1.4.7 - version: 1.4.7 + version: 1.4.7(supports-color@7.2.0) eslint: specifier: ^8.57.1 - version: 8.57.1 + version: 8.57.1(supports-color@7.2.0) eslint-config-next: - specifier: ^15.5.10 - version: 15.5.20(eslint@8.57.1)(typescript@5.9.3) + specifier: ^15.5.24 + version: 15.5.24(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) postcss: specifier: ^8.5.10 version: 8.5.19 @@ -382,60 +382,60 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@15.5.20': - resolution: {integrity: sha512-dXh51Wvddf8daEyBXryZZEe1FdVxEWx9lgaTseLZUtC1XP/W8Wri+Z+VPOElHlByk23CyqHdc2oVByX7wsTWsw==} + '@next/env@15.5.24': + resolution: {integrity: sha512-mBDF7T0XKZjs9SpUAl0buizVO+O02ULjOvWX8o/AZo/5AGw/UAS1Zzcylmd4pqbftzmKQi+L/nB4jgBYKEAl5Q==} - '@next/eslint-plugin-next@15.5.20': - resolution: {integrity: sha512-MZUgFpVd9rGSZpb8bNceUWvkAZe6aQw/6h2SSqHQuYzKfaiEUEVPIO6mXqaBmiBEBSN1f1sOc1uV8GCM90oegg==} + '@next/eslint-plugin-next@15.5.24': + resolution: {integrity: sha512-K6jcYhwMY73qpeJ5SP+rrXs7Fuvv94HEvOCEen/zjwG7aq83utZY8n+8YdKHZR1lv0n5cB4z9C6QqwpE9acN+A==} - '@next/swc-darwin-arm64@15.5.20': - resolution: {integrity: sha512-in0yXG7/pRBVjWeEl7f7ZZETpletSMFKXVS4GJgHENTPVrJFNJKPrYewa9rpZcvdjwFece5fZP0CK34G4PxowA==} + '@next/swc-darwin-arm64@15.5.24': + resolution: {integrity: sha512-AGdNLvxZNY6eR2iSnV+6wUa8CiHTMr4F7g3uHH7fT4ICIJBE00R9u4tzN/Vuwsw0cOi8MTD2HJcTCb6siMH88Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.20': - resolution: {integrity: sha512-0hsFshdPnTzGJdDTHeHJ+XPUShOpnyp9pUFDwDhqctsA0Cd8NcIVGRPtptYhgYY9DjkKgCDRkXxmgRc+CgT5Wg==} + '@next/swc-darwin-x64@15.5.24': + resolution: {integrity: sha512-9HrQajBMmGcrrrvDfRimiCrbAPh3E6uHJmwBovYr6Yrmi9p9PZqI876BrXX280wICh3o2XwUlp4blkB0NNBqFg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.20': - resolution: {integrity: sha512-DMvkoBtAABOzE6pMZRW/xNm7sKqql3wzzzZJ1R/d/rp4BCxv6LykouD3tHjGY8WdQqGpZs11t+R9AtjPxvvljw==} + '@next/swc-linux-arm64-gnu@15.5.24': + resolution: {integrity: sha512-rl9LSfE75si0WT3cDgdUC1XYCKS+TgxC+/IjitmeycrAG18X/plIP1/vy8dd/HPycYcIvE688PD7FuvEAiEAew==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@15.5.20': - resolution: {integrity: sha512-RQmDfeYBtXV2FSId7dfA1hE6M/T6+g7wdbYnFQ47tw/gUBwV+CccLVejNmCGa9yLDitk83foeg8hl/3DjfYQ5g==} + '@next/swc-linux-arm64-musl@15.5.24': + resolution: {integrity: sha512-TlNAnpsjxSF3aAUtqnfmtXXf8m9sIDBlmF3c7bTAlnshUYu2U0OxN2uf5d0gcFwqHVEdivJNBcCaqNOwPGNimw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@15.5.20': - resolution: {integrity: sha512-DkWLEdKajJwdGt27M3i1VEO2kelTvZrK6Pcb7JvW2BY+nofWm7FBsBNDj7g7Pr1NuQ5PLJvqEqYa20GTsBDnKQ==} + '@next/swc-linux-x64-gnu@15.5.24': + resolution: {integrity: sha512-7dwtlhr0SLndqTG1z9ncRkbJswDZiKWlxzFyXDvJ2RDZRDRHp8zyMJ4D9UH/FgnQeXxxB6gZy2pMcIUoNKQ4pA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@15.5.20': - resolution: {integrity: sha512-rAO5b7pKHvX+ExdmJskusDXTNbiNZfptifIPZItbUx+AOXxxTydVBsPt7Oz84DRd5mY8e0DcE8kvLj3AIfjE6w==} + '@next/swc-linux-x64-musl@15.5.24': + resolution: {integrity: sha512-kGZxM+WhkYs0276lFrMkj7PRtXT3Btp6cwvfSO/cCVLxJJttB5Ccnl2niaCgUja8HgSbEVnMHpg3FJWoOJ9e/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@15.5.20': - resolution: {integrity: sha512-Hp3zFsN8N8Kj9+vY6L4vnZ9EtA9eXyATu0q4EfGbZTiocgPUNSfz8NWhym6xvaOmHpJ8EuoypuU1WejCPsTFtg==} + '@next/swc-win32-arm64-msvc@15.5.24': + resolution: {integrity: sha512-jBDDkZ/qKAqkWivWDMkJSXUzbzV0QKRBKJjEHUAvSB97Hzw7NLzJ6yV56Lts/wjir7s4P31GYgpbS6ZL+hasAA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.20': - resolution: {integrity: sha512-T/L7CXpR1M0wij/xbF3rT1+7KvSkfOLr7C+ToHHWZTG2eKmb52C5WvsyGCBNtkVvDEUESWkRUbbqSH4rSbOCYQ==} + '@next/swc-win32-x64-msvc@15.5.24': + resolution: {integrity: sha512-JqtwjvvorjacQ0spgjmUJoxySoYgPwdT1sFdQ0/zmW4iMlP2hjYlCoJIyS7o6Epb4Fug8eco3HXFoBDUCDeH7Q==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1549,8 +1549,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@15.5.20: - resolution: {integrity: sha512-Pl/I5544gmkcVVWcnaOMfhJSBK8lZ1NCJ+mGBkd2qvbGt2Gi7sEpgHF06OR13a2p6THODlncpvGsZzY2vUqwxw==} + eslint-config-next@15.5.24: + resolution: {integrity: sha512-RXne8azyj9DKV9N15elyRsnh98BtKINYHCG7bmVhpIhaOtaYvrNJ37XwWpjY9Z09f5RHYOf9vn5uU0MgV1iLrw==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -2184,8 +2184,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.5.20: - resolution: {integrity: sha512-cvyS3/geydan1xLtE3FA8VCgdoQ/Gg/dlOldFkFCbB5VcVYJV7090hQLBnvTW2PwT76Z/dHdzDZCsVhZpoOlUA==} + next@15.5.24: + resolution: {integrity: sha512-Y+xn8EQCoC3ZbsFPyzE+tE8XOdrWeUdUF7NeXbmg9DsgAxl5UYxlsrvgVESHTyTGigoTa1bCUrxn70F5bqt0Gw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -2882,7 +2882,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -2890,7 +2890,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -2920,17 +2920,17 @@ snapshots: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1(supports-color@7.2.0))': dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/eslintrc@2.1.4': + '@eslint/eslintrc@2.1.4(supports-color@7.2.0)': dependencies: ajv: 6.15.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -2960,10 +2960,10 @@ snapshots: '@floating-ui/utils@0.2.12': {} - '@humanwhocodes/config-array@0.13.0': + '@humanwhocodes/config-array@0.13.0(supports-color@7.2.0)': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -3090,34 +3090,34 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@15.5.20': {} + '@next/env@15.5.24': {} - '@next/eslint-plugin-next@15.5.20': + '@next/eslint-plugin-next@15.5.24': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.5.20': + '@next/swc-darwin-arm64@15.5.24': optional: true - '@next/swc-darwin-x64@15.5.20': + '@next/swc-darwin-x64@15.5.24': optional: true - '@next/swc-linux-arm64-gnu@15.5.20': + '@next/swc-linux-arm64-gnu@15.5.24': optional: true - '@next/swc-linux-arm64-musl@15.5.20': + '@next/swc-linux-arm64-musl@15.5.24': optional: true - '@next/swc-linux-x64-gnu@15.5.20': + '@next/swc-linux-x64-gnu@15.5.24': optional: true - '@next/swc-linux-x64-musl@15.5.20': + '@next/swc-linux-x64-musl@15.5.24': optional: true - '@next/swc-win32-arm64-msvc@15.5.20': + '@next/swc-win32-arm64-msvc@15.5.24': optional: true - '@next/swc-win32-x64-msvc@15.5.20': + '@next/swc-win32-x64-msvc@15.5.24': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3643,15 +3643,15 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.64.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -3659,23 +3659,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 - eslint: 8.57.1 + debug: 4.4.3(supports-color@7.2.0) + eslint: 8.57.1(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.64.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) '@typescript-eslint/types': 8.64.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3689,13 +3689,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.64.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.64.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.3 - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + debug: 4.4.3(supports-color@7.2.0) + eslint: 8.57.1(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -3703,13 +3703,13 @@ snapshots: '@typescript-eslint/types@8.64.0': {} - '@typescript-eslint/typescript-estree@8.64.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.64.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.64.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.64.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@5.9.3) '@typescript-eslint/types': 8.64.0 '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.5 semver: 7.8.5 tinyglobby: 0.2.17 @@ -3718,13 +3718,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.64.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.64.0 '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(typescript@5.9.3) - eslint: 8.57.1 + '@typescript-eslint/typescript-estree': 8.64.0(supports-color@7.2.0)(typescript@5.9.3) + eslint: 8.57.1(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -4121,13 +4121,17 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - debug@3.2.7: + debug@3.2.7(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 deep-is@0.1.4: {} @@ -4147,15 +4151,15 @@ snapshots: defu@6.1.7: {} - depcheck@1.4.7: + depcheck@1.4.7(supports-color@7.2.0): dependencies: '@babel/parser': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@vue/compiler-sfc': 3.5.39 callsite: 1.0.0 camelcase: 6.3.0 cosmiconfig: 7.1.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) deps-regex: 0.2.0 findup-sync: 5.0.0 ignore: 5.3.2 @@ -4338,19 +4342,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.5.20(eslint@8.57.1)(typescript@5.9.3): + eslint-config-next@15.5.24(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 15.5.20 + '@next/eslint-plugin-next': 15.5.24 '@rushstack/eslint-patch': 1.16.1 - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.64.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-react: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) + '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + eslint: 8.57.1(supports-color@7.2.0) + eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) + eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1(supports-color@7.2.0)) + eslint-plugin-react: 7.37.5(eslint@8.57.1(supports-color@7.2.0)) + eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1(supports-color@7.2.0)) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -4358,52 +4362,52 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-import-resolver-node@0.3.10: + eslint-import-resolver-node@0.3.10(supports-color@7.2.0): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) is-core-module: 2.16.2 resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3 - eslint: 8.57.1 + debug: 4.4.3(supports-color@7.2.0) + eslint: 8.57.1(supports-color@7.2.0) get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@8.57.1)(typescript@5.9.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) + '@typescript-eslint/parser': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + eslint: 8.57.1(supports-color@7.2.0) + eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint: 8.57.1(supports-color@7.2.0) + eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -4415,13 +4419,13 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.64.0(eslint@8.57.1(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): + eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1(supports-color@7.2.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -4431,7 +4435,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) hasown: 2.0.4 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -4440,11 +4444,11 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@8.57.1): + eslint-plugin-react-hooks@5.2.0(eslint@8.57.1(supports-color@7.2.0)): dependencies: - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) - eslint-plugin-react@7.37.5(eslint@8.57.1): + eslint-plugin-react@7.37.5(eslint@8.57.1(supports-color@7.2.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -4452,7 +4456,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.4.0 - eslint: 8.57.1 + eslint: 8.57.1(supports-color@7.2.0) estraverse: 5.3.0 hasown: 2.0.4 jsx-ast-utils: 3.3.5 @@ -4475,20 +4479,20 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@8.57.1: + eslint@8.57.1(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 + '@eslint/eslintrc': 2.1.4(supports-color@7.2.0) '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/config-array': 0.13.0(supports-color@7.2.0) '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 '@ungap/structured-clone': 1.3.3 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -5055,9 +5059,9 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - next@15.5.20(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@15.5.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@next/env': 15.5.20 + '@next/env': 15.5.24 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001805 postcss: 8.4.31 @@ -5065,14 +5069,14 @@ snapshots: react-dom: 19.2.7(react@19.2.7) styled-jsx: 5.1.6(react@19.2.7) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.20 - '@next/swc-darwin-x64': 15.5.20 - '@next/swc-linux-arm64-gnu': 15.5.20 - '@next/swc-linux-arm64-musl': 15.5.20 - '@next/swc-linux-x64-gnu': 15.5.20 - '@next/swc-linux-x64-musl': 15.5.20 - '@next/swc-win32-arm64-msvc': 15.5.20 - '@next/swc-win32-x64-msvc': 15.5.20 + '@next/swc-darwin-arm64': 15.5.24 + '@next/swc-darwin-x64': 15.5.24 + '@next/swc-linux-arm64-gnu': 15.5.24 + '@next/swc-linux-arm64-musl': 15.5.24 + '@next/swc-linux-x64-gnu': 15.5.24 + '@next/swc-linux-x64-musl': 15.5.24 + '@next/swc-win32-arm64-msvc': 15.5.24 + '@next/swc-win32-x64-msvc': 15.5.24 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' diff --git a/tests/nutrition.test.ts b/tests/nutrition.test.ts new file mode 100644 index 0000000..b3bf6ff --- /dev/null +++ b/tests/nutrition.test.ts @@ -0,0 +1,58 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { getEnergyKcal, getNutrientValue } from "../lib/nutrition.ts"; +import { formatNutritionData, sortProducts, type Product } from "../lib/openFoodFactsApi.ts"; + +test("prefere energia explícita em kcal e preserva zero", () => { + assert.equal(getEnergyKcal({ "energy-kcal_100g": 539, energy_100g: 2255 }), 539); + assert.equal(getEnergyKcal({ "energy-kcal_100g": 0, energy_100g: 418.4 }), 0); + assert.equal(getNutrientValue(0), 0); + assert.equal(getNutrientValue("0"), 0); +}); + +test("converte kJ para kcal quando o campo kcal está ausente", () => { + assert.ok(Math.abs(getEnergyKcal({ energy_100g: 418.4 })! - 100) < 1e-10); + assert.ok(Math.abs(getEnergyKcal({ "energy-kj_100g": 418.4 })! - 100) < 1e-10); + assert.equal(getEnergyKcal(), null); + assert.equal(getEnergyKcal({}), null); +}); + +test("valores ausentes, negativos e não finitos não entram nos cálculos", () => { + for (const value of [null, undefined, "", " ", "garbage", "12g", true, -1, NaN, Infinity, "Infinity"]) { + assert.equal(getNutrientValue(value), null); + } + assert.equal(getEnergyKcal({ "energy-kcal_100g": NaN, energy_100g: Infinity }), null); + assert.equal(getNutrientValue("12.5"), 12.5); +}); + +test("formata energia e nutrientes com zero sem confundir com dados ausentes", () => { + const formatted = formatNutritionData({ code: "zero", product_name: "Zero", nutriments: { + "energy-kcal_100g": 0, energy_100g: 100, fat_100g: 0, + carbohydrates_100g: 0, sodium_100g: 0, + } }); + assert.equal(formatted?.energy, "0 kcal"); + assert.equal(formatted?.fat, "0.0g"); + assert.equal(formatted?.carbs, "0.0g"); + assert.equal(formatted?.carbohydrates, "0.0g"); + assert.equal(formatted?.sodium, "0.00g"); + assert.equal(formatted?.proteins, "N/A"); +}); + +test("formata kcal explícitas e converte o fallback em kJ", () => { + const base = { code: "a", product_name: "A" }; + assert.equal(formatNutritionData({ ...base, nutriments: { "energy-kcal_100g": 539, energy_100g: 2255 } })?.energy, "539 kcal"); + assert.equal(formatNutritionData({ ...base, nutriments: { energy_100g: 418.4 } })?.energy, "100 kcal"); + assert.equal(formatNutritionData({ ...base, nutriments: { fat_100g: Infinity } })?.fat, "N/A"); +}); + +test("ordena por kcal, preserva zero e deixa dados ausentes no fim nas duas direções", () => { + const products: Product[] = [ + { code: "unknown", product_name: "Unknown" }, + { code: "kj", product_name: "kJ", nutriments: { energy_100g: 418.4 } }, + { code: "zero", product_name: "Zero", nutriments: { "energy-kcal_100g": 0 } }, + { code: "kcal", product_name: "kcal", nutriments: { "energy-kcal_100g": 50 } }, + ]; + assert.deepEqual(sortProducts(products, "energy").map(p => p.code), ["zero", "kcal", "kj", "unknown"]); + assert.deepEqual(sortProducts(products, "energy", "desc").map(p => p.code), ["kj", "kcal", "zero", "unknown"]); + assert.equal(products[0].code, "unknown"); +}); diff --git a/tsconfig.json b/tsconfig.json index f15eefe..92af8e9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "skipLibCheck": true, "strict": true, "noEmit": true, + "allowImportingTsExtensions": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler",