refactor shared frontend size parsing

This commit is contained in:
Dariusz L
2026-08-06 22:30:15 +02:00
parent 76d0a3aa7c
commit cf4697ddd8
5 changed files with 75 additions and 4 deletions
@@ -31,6 +31,7 @@ import {
} from '../web/resolver/utils/category_aliases.generated.js';
import { getSha256Field, normalizeSha256 } from '../web/resolver/utils/hash_utils.js';
import { normalizeSearchToken } from '../web/resolver/utils/search_utils.js';
import { parseFiniteNumber } from '../web/resolver/utils/size_utils.js';
import { getSourceDisplayLabel, normalizeSourceKey } from '../web/resolver/utils/source_labels.js';
import { baseModelAliasMethods } from '../web/resolver/search/base_model_alias_methods.js';
import { searchHashMethods } from '../web/resolver/search/search_hash_methods.js';
@@ -6963,6 +6964,50 @@ test('CivitAI hash search renders sizeKB when byte size is unavailable', () => {
);
});
test('search result size formatting preserves aliases and fallback values', () => {
const formatSearchResultSize = eval(`(${extractMethod(searchPanelMethodsSource, 'formatSearchResultSize')})`);
const dialog = {
formatBytes(value) {
return `${Number(value)} B`;
},
};
assert.equal(formatSearchResultSize.call(dialog, { size: 2048 }), '2048 B');
assert.equal(formatSearchResultSize.call(dialog, { size: '5.7 GB' }), '5.7 GB');
assert.equal(
formatSearchResultSize.call(dialog, {
size: null,
file_info: { sizeBytes: 1024 },
}),
'1024 B'
);
assert.equal(formatSearchResultSize.call(dialog, { size: null, sizeKB: '2' }), '2048 B');
assert.equal(formatSearchResultSize.call(dialog, { size: 0 }), '0 B');
assert.equal(formatSearchResultSize.call(dialog, { size: null, sizeKB: 'invalid' }), '');
assert.equal(formatSearchResultSize.call(dialog, { size: null, sizeKB: Number.MAX_VALUE }), '');
});
test('info dialog size formatting preserves numeric, nested and label values', () => {
const formatInfoDialogSize = eval(`(${extractMethod(modelInfoMethodsSource, 'formatInfoDialogSize')})`);
const dialog = {
formatBytes(value) {
return `${Number(value)} B`;
},
};
assert.equal(formatInfoDialogSize.call(dialog, { size: 2048 }), '2048 B');
assert.equal(formatInfoDialogSize.call(dialog, { size: '2048' }), '2048 B');
assert.equal(
formatInfoDialogSize.call(dialog, { file: { sizeBytes: 1024 } }),
'1024 B'
);
assert.equal(formatInfoDialogSize.call(dialog, { size: '5.7 GB' }), '5.7 GB');
assert.equal(formatInfoDialogSize.call(dialog, { size: 0 }), '0 B');
assert.equal(formatInfoDialogSize.call(dialog, { size: '0' }), '0 B');
assert.equal(formatInfoDialogSize.call(dialog, { size: 'invalid' }), 'invalid');
assert.equal(formatInfoDialogSize.call(dialog, {}), '');
});
test('link/name controls place the add or search action before the input switch', () => {
const renderSearchControls = eval(`(${extractMethod(searchPanelMethodsSource, 'renderSearchControls')})`);
const dialog = {
+18
View File
@@ -0,0 +1,18 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseFiniteNumber } from '../web/resolver/utils/size_utils.js';
test('parseFiniteNumber normalizes numeric size values', () => {
assert.equal(parseFiniteNumber(2048), 2048);
assert.equal(parseFiniteNumber('2048'), 2048);
assert.equal(parseFiniteNumber('0'), 0);
assert.equal(parseFiniteNumber(0), 0);
});
test('parseFiniteNumber rejects empty, invalid and non-finite values', () => {
assert.equal(parseFiniteNumber(null), null);
assert.equal(parseFiniteNumber(undefined), null);
assert.equal(parseFiniteNumber(''), null);
assert.equal(parseFiniteNumber('invalid'), null);
assert.equal(parseFiniteNumber(Infinity), null);
});
+3 -2
View File
@@ -4,6 +4,7 @@ import { getSourceDisplayLabel, normalizeSourceKey } from "../utils/source_label
import { getCivitaiModelUrl } from "../globals.js";
import { safeStorage, normalizePathIdentity } from "../utils/html_utils.js";
import { getSha256Field } from "../utils/hash_utils.js";
import { parseFiniteNumber } from "../utils/size_utils.js";
import { bindEventOnce } from "../utils/dom_patch_utils.js";
import {
classifyLocalMatches,
@@ -1552,8 +1553,8 @@ export const searchPanelMethods = {
const sizeKb = candidate.sizeKB ?? candidate.size_kb;
if (sizeKb !== undefined && sizeKb !== null && sizeKb !== '') {
const sizeBytes = Number(sizeKb) * 1024;
if (Number.isFinite(sizeBytes)) return this.formatBytes(sizeBytes);
const sizeBytes = parseFiniteNumber(sizeKb * 1024);
if (sizeBytes !== null) return this.formatBytes(sizeBytes);
}
}
+6
View File
@@ -0,0 +1,6 @@
export function parseFiniteNumber(value) {
if (value === null || value === undefined || value === '') return null;
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : null;
}
+3 -2
View File
@@ -4,6 +4,7 @@ import { escapeHtml, escapeJsString, getFilenameFromPath, sanitizeDescriptionHtm
import { getModelCardUrl } from "../utils/url_utils.js";
import { extractComfyWorkflow } from "../utils/workflow_metadata.js";
import { getSha256Field, normalizeSha256 } from "../utils/hash_utils.js";
import { parseFiniteNumber } from "../utils/size_utils.js";
import { getSourceDisplayLabel } from "../utils/source_labels.js";
import { getCivitaiModelUrl } from "../globals.js";
export const modelInfoMethods = {
@@ -1263,8 +1264,8 @@ export const modelInfoMethods = {
if (rawSize === null || rawSize === undefined || rawSize === '') return '';
if (rawSize === 0 || rawSize === '0') return '0 B';
const numericSize = Number(rawSize);
if (Number.isFinite(numericSize)) {
const numericSize = parseFiniteNumber(rawSize);
if (numericSize !== null) {
return typeof this.formatBytes === 'function'
? this.formatBytes(numericSize)
: `${numericSize} B`;