Refactor cache handling for plugins: update pluginsData initialization and improve fetch logic; enhance UI tests for translation loading

This commit is contained in:
Jokob @NetAlertX
2026-07-02 04:32:01 +00:00
parent 268347e187
commit 35b19cbb51
3 changed files with 41 additions and 18 deletions

View File

@@ -178,8 +178,10 @@ function cacheApiConfig() {
// Module-level declaration so cachePluginStrings() and other callers can
// safely reference pluginsData even before cacheSettings() has fetched it.
// Populated (or repopulated) by cacheSettings() on every full fetch.
var pluginsData = [];
// null = fetch not yet attempted or fetch failed (unknown state).
// [] = fetch succeeded and confirmed no plugins are configured.
// [...] = fetch succeeded and plugins are present.
var pluginsData = null;
function cacheSettings()
{
@@ -192,12 +194,18 @@ function cacheSettings()
// so the extra fetch would be pointless on every subsequent warm load.
if (getCache(CACHE_KEYS.initFlag('cachePluginStrings_v1')) !== 'true') {
fetchJson('plugins.json')
.catch(() => [])
.then((pluginsArr) => {
if (Array.isArray(pluginsArr) && pluginsArr.length > 0) {
// Only update pluginsData on a successful fetch so that a network
// failure is not misread as "no plugins configured" (null sentinel).
if (Array.isArray(pluginsArr)) {
pluginsData = pluginsArr;
}
resolve();
})
.catch(() => {
// Fetch failed — leave pluginsData as null (unknown state) so
// cachePluginStrings() retries rather than treating it as no-op.
resolve();
});
return;
}
@@ -312,10 +320,20 @@ function cacheStrings() {
return new Promise((resolve, reject) => {
if(getCache(CACHE_KEYS.initFlag('cacheStrings_v2')) === "true")
{
// Core strings are already cached. Plugin strings are now handled by
// the dedicated cachePluginStrings() step — nothing to do here.
resolve();
return;
// Verify STRINGS_COUNT still matches the current language configuration.
// A language switch without a full cache clear leaves the flag set but
// the count wrong, causing isAppInitialized() to deadlock.
const expectedCount = getLangCode() === 'en_us' ? 1 : 2;
const currentCount = parseInt(getCache(CACHE_KEYS.STRINGS_COUNT)) || 0;
if (currentCount === expectedCount) {
resolve();
return;
}
// Mismatch — reset count and fall through to reload strings for the
// current language set. The flag stays set; handleSuccess re-increments.
console.log(`[cacheStrings] STRINGS_COUNT mismatch (stored: ${currentCount}, expected: ${expectedCount}) — reloading strings.`);
setCache(CACHE_KEYS.STRINGS_COUNT, 0);
// Do NOT return — fall through to full load path below.
}
// Create a promise for each language (include en_us by default as fallback)
@@ -386,7 +404,10 @@ function cachePluginStrings() {
// No plugin strings returned. If plugins are loaded the file may not
// be ready yet — reject so retryStep() retries. If no plugins are
// loaded at all, empty strings are expected: succeed.
const hasPlugins = Array.isArray(pluginsData) && pluginsData.length > 0;
// null = plugins.json fetch failed (unknown) — retry to be safe.
// [] = confirmed no plugins — accept empty strings as valid.
// [...] = plugins present — retry, file may not be ready yet.
const hasPlugins = pluginsData === null || (Array.isArray(pluginsData) && pluginsData.length > 0);
if (hasPlugins) {
console.warn('[cachePluginStrings] 0 entries returned but plugins are loaded — file may not be ready yet. Will retry.');
reject(new Error('Plugin language strings empty'));

View File

@@ -312,7 +312,7 @@ async function getData() {
// module-level fetchJson defined in cache.js, which returns res['data'] and
// is used by cachePluginStrings() during app initialization.
async function fetchPluginJson(filename) {
const response = await fetch(`php/server/query_json.php?file=${filename}`);
const response = await fetch(`php/server/query_json.php?file=${encodeURIComponent(filename)}&nocache=${Date.now()}`);
if (!response.ok) throw new Error(`Failed to load ${filename}`);
return await response.json();
}

View File

@@ -9,7 +9,6 @@ PRD: Reliable Loading of Plugin Translation Strings
import sys
import os
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
@@ -161,16 +160,19 @@ def test_plugins_page_no_undefined_strings_after_cache_clear(driver):
_wait_for_app_init(driver)
# Give the tabs a moment to render after init completes
time.sleep(2)
# Wait for getData() to complete: either tab links or the empty-state
# paragraph will appear once generateTabs() has run.
WebDriverWait(driver, 10).until(
lambda d: len(d.find_elements(
By.CSS_SELECTOR,
"#tabs-location a, #tabs-content-location p.text-muted"
)) > 0
)
# Allow the word 'undefined' inside code/technical blocks but not as a
# standalone visible label replacing a translation string.
# We check that it does not appear as a trimmed standalone word in headings/tabs.
tab_labels = driver.find_elements(By.CSS_SELECTOR, "#tabs-location a, .tab-label, h5")
for el in tab_labels:
label = el.text.strip()
assert label != "undefined", (
f"Tab/heading label is literally 'undefined' — plugin strings were not "
f"loaded before getData() rendered the tabs on plugins.php."
"Tab/heading label is literally 'undefined' — plugin strings were not "
"loaded before getData() rendered the tabs on plugins.php."
)