mirror of
https://github.com/LizardByte/GameDB.git
synced 2026-10-23 03:36:51 +09:00
fix: migrate item_loader from jQuery to fetch (#266)
This commit is contained in:
1 parent
ccd883a7d2
commit
074c8b00e0
4 files changed
+163
-123
No files matched your search
@@ -15,7 +15,6 @@ export default [
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
...globals.jquery,
|
||||
// cross-file globals injected by item_detail.js into the browser scope
|
||||
"base_url": "readonly",
|
||||
"base_path": "readonly",
|
||||
|
||||
@@ -400,108 +400,126 @@ function createPlatformCardElement(platform, platform_region_flag_map, metadata_
|
||||
return column;
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
// Set cache = false for all jquery ajax requests.
|
||||
$.ajaxSetup({
|
||||
cache: false,
|
||||
})
|
||||
/**
|
||||
* Map platform release regions to display flags and Bootstrap font sizes.
|
||||
*
|
||||
* @type {Object.<string, {code: string, size: string}>}
|
||||
*/
|
||||
const platform_region_flag_map = {
|
||||
"europe": {
|
||||
"code": String.fromCodePoint(0x1F1EA, 0x1F1FA),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"north_america": {
|
||||
"code": String.fromCodePoint(0x1F1FA, 0x1F1F8),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"australia": {
|
||||
"code": String.fromCodePoint(0x1F1E6, 0x1F1FA),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"new_zealand": {
|
||||
"code": String.fromCodePoint(0x1F1F3, 0x1F1FF),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"japan": {
|
||||
"code": String.fromCodePoint(0x1F1EF, 0x1F1F5),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"china": {
|
||||
"code": String.fromCodePoint(0x1F1E8, 0x1F1F3),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"asia": {
|
||||
"code": String.fromCodePoint(0x1F30F),
|
||||
"size": "fs-4",
|
||||
},
|
||||
"worldwide": {
|
||||
"code": String.fromCodePoint(0x1F30E),
|
||||
"size": "fs-4",
|
||||
},
|
||||
"korea": {
|
||||
"code": String.fromCodePoint(0x1F1F0, 0x1F1F7),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"brazil": {
|
||||
"code": String.fromCodePoint(0x1F1E7, 0x1F1F7),
|
||||
"size": "fs-2",
|
||||
},
|
||||
}
|
||||
|
||||
// get platform cross-reference from json
|
||||
let platform_xref
|
||||
let get_platform_xref = function() {
|
||||
$.ajax({
|
||||
url: `${base_url}/platforms/cross-reference.json`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
async: false, // this is false, so we can set the platform_xref variable
|
||||
success: function (result) {
|
||||
platform_xref = result
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Map platform version metadata keys to Material Symbols icon names.
|
||||
*
|
||||
* Keys with null values are rendered by specialized handlers.
|
||||
*
|
||||
* @type {Object.<string, string|null>}
|
||||
*/
|
||||
const metadata_key_icon_map = {
|
||||
// material icons
|
||||
'os': 'code_blocks',
|
||||
'cpu': 'memory',
|
||||
'graphics': 'developer_board',
|
||||
'memory': 'memory_alt',
|
||||
'storage': 'storage',
|
||||
'media': 'save',
|
||||
'connectivity': 'cable',
|
||||
'output': 'settings_input_component',
|
||||
'resolutions': 'aspect_ratio',
|
||||
'sound': 'volume_up',
|
||||
// these will be processed slightly differently
|
||||
'platform_version_release_dates': null,
|
||||
'summary': null,
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a JSON resource without using the browser HTTP cache.
|
||||
*
|
||||
* @param {string} url - URL to load.
|
||||
* @returns {Promise<object>} Parsed JSON response.
|
||||
*/
|
||||
function fetchJson(url) {
|
||||
return fetch(url, { cache: "no-store" }).then(response => response.json());
|
||||
}
|
||||
|
||||
/**
|
||||
* Render platform cards after enriching IGDB platform data with cross-reference metadata.
|
||||
*
|
||||
* @param {Object.<string, object>} result - Platform records keyed by source identifier.
|
||||
* @param {Object.<string, object>} platform_xref - Platform cross-reference records.
|
||||
* @returns {void}
|
||||
*/
|
||||
function renderPlatformCards(result, platform_xref) {
|
||||
const platforms = processPlatformsData(result, platform_xref);
|
||||
const sorted = platforms.toSorted(globalThis.rankingSorter("name", "id")).reverse();
|
||||
|
||||
for(let item in sorted) {
|
||||
const column = createPlatformCardElement(sorted[item], platform_region_flag_map, metadata_key_icon_map, base_path);
|
||||
platforms_container.appendChild(column);
|
||||
}
|
||||
}
|
||||
|
||||
let platform_region_flag_map = {
|
||||
"europe": {
|
||||
"code": String.fromCodePoint(0x1F1EA, 0x1F1FA),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"north_america": {
|
||||
"code": String.fromCodePoint(0x1F1FA, 0x1F1F8),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"australia": {
|
||||
"code": String.fromCodePoint(0x1F1E6, 0x1F1FA),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"new_zealand": {
|
||||
"code": String.fromCodePoint(0x1F1F3, 0x1F1FF),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"japan": {
|
||||
"code": String.fromCodePoint(0x1F1EF, 0x1F1F5),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"china": {
|
||||
"code": String.fromCodePoint(0x1F1E8, 0x1F1F3),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"asia": {
|
||||
"code": String.fromCodePoint(0x1F30F),
|
||||
"size": "fs-4",
|
||||
},
|
||||
"worldwide": {
|
||||
"code": String.fromCodePoint(0x1F30E),
|
||||
"size": "fs-4",
|
||||
},
|
||||
"korea": {
|
||||
"code": String.fromCodePoint(0x1F1F0, 0x1F1F7),
|
||||
"size": "fs-2",
|
||||
},
|
||||
"brazil": {
|
||||
"code": String.fromCodePoint(0x1F1E7, 0x1F1F7),
|
||||
"size": "fs-2",
|
||||
},
|
||||
}
|
||||
/**
|
||||
* Load platform cross-reference and platform data, then render the platform card grid.
|
||||
*
|
||||
* @returns {Promise<void>} Resolves when platform cards have been rendered.
|
||||
*/
|
||||
function initializePlatformCards() {
|
||||
return fetchJson(`${base_url}/platforms/cross-reference.json`)
|
||||
.then(platform_xref => fetchJson(`${base_url}/platforms/all.json`)
|
||||
.then(result => {
|
||||
renderPlatformCards(result, platform_xref);
|
||||
}));
|
||||
}
|
||||
|
||||
let metadata_key_icon_map = {
|
||||
// material icons
|
||||
'os': 'code_blocks',
|
||||
'cpu': 'memory',
|
||||
'graphics': 'developer_board',
|
||||
'memory': 'memory_alt',
|
||||
'storage': 'storage',
|
||||
'media': 'save',
|
||||
'connectivity': 'cable',
|
||||
'output': 'settings_input_component',
|
||||
'resolutions': 'aspect_ratio',
|
||||
'sound': 'volume_up',
|
||||
// these will be processed slightly differently
|
||||
'platform_version_release_dates': null,
|
||||
'summary': null,
|
||||
}
|
||||
|
||||
|
||||
// create platform cards
|
||||
let initialize = function(){
|
||||
$.ajax({
|
||||
url: `${base_url}/platforms/all.json`,
|
||||
type: "GET",
|
||||
dataType:"json",
|
||||
success: function (result) {
|
||||
const platforms = processPlatformsData(result, platform_xref);
|
||||
const sorted = platforms.toSorted(globalThis.rankingSorter("name", "id")).reverse();
|
||||
|
||||
for(let item in sorted) {
|
||||
const column = createPlatformCardElement(sorted[item], platform_region_flag_map, metadata_key_icon_map, base_path);
|
||||
platforms_container.appendChild(column);
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
if (typeof module === "undefined") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initializePlatformCards().catch(error => {
|
||||
console.error("Failed to initialize platform cards", error);
|
||||
});
|
||||
}
|
||||
|
||||
get_platform_xref()
|
||||
initialize()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for games by name across all buckets.
|
||||
@@ -609,6 +627,9 @@ if (typeof module !== "undefined") {
|
||||
addMetadataItemToFooter,
|
||||
processPlatformsData,
|
||||
createPlatformCardElement,
|
||||
fetchJson,
|
||||
renderPlatformCards,
|
||||
initializePlatformCards,
|
||||
run_search,
|
||||
};
|
||||
}
|
||||
@@ -11,9 +11,8 @@ after-content:
|
||||
ext-css:
|
||||
- href: https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200
|
||||
ext-js:
|
||||
- https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
|
||||
- https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.314.32913/dist/levenshtein-distance.js
|
||||
- https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.314.32913/dist/ranking-sorter.js
|
||||
- href: "https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.314.32913/dist/levenshtein-distance.js"
|
||||
- href: "https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.314.32913/dist/ranking-sorter.js"
|
||||
js:
|
||||
- /GameDB/assets/js/item_loader.js
|
||||
---
|
||||
|
||||
+43
-22
@@ -22,28 +22,6 @@ document.body.innerHTML = `
|
||||
<input id="search_term" value="" />
|
||||
`;
|
||||
|
||||
// Mock jQuery — fire ready callback synchronously, and fire ajax success immediately
|
||||
globalThis.$ = function() {
|
||||
return { ready: (fn) => fn() };
|
||||
};
|
||||
globalThis.$.ajaxSetup = () => {};
|
||||
globalThis.$.ajax = function(opts) {
|
||||
if (opts?.success) {
|
||||
// Return mock data appropriate to the URL
|
||||
if (opts?.url?.includes('cross-reference')) {
|
||||
opts.success({});
|
||||
} else if (opts?.url?.includes('all.json')) {
|
||||
opts.success({
|
||||
'1': {
|
||||
id: 1, name: 'PC', url: 'https://igdb.com', summary: 'Personal computer.',
|
||||
screenscraper_id: null, screenscraper_region: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
globalThis.jQuery = globalThis.$;
|
||||
|
||||
const {
|
||||
splitString,
|
||||
fetchGameData,
|
||||
@@ -58,6 +36,8 @@ const {
|
||||
addMetadataItemToFooter,
|
||||
processPlatformsData,
|
||||
createPlatformCardElement,
|
||||
fetchJson,
|
||||
initializePlatformCards,
|
||||
run_search,
|
||||
} = require('../gh-pages-template/assets/js/item_loader.js');
|
||||
|
||||
@@ -117,6 +97,16 @@ describe('item_loader.js', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchJson', () => {
|
||||
test('fetches JSON with no-store cache behavior', async () => {
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
json: () => Promise.resolve({ ok: true }),
|
||||
});
|
||||
await expect(fetchJson('/data.json')).resolves.toEqual({ ok: true });
|
||||
expect(globalThis.fetch).toHaveBeenCalledWith('/data.json', { cache: 'no-store' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGameCard', () => {
|
||||
test('renders card with cover image', () => {
|
||||
const col = createGameCard(1, {
|
||||
@@ -398,7 +388,38 @@ describe('item_loader.js', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializePlatformCards', () => {
|
||||
test('loads cross-reference before rendering platform cards', async () => {
|
||||
globalThis.fetch = jest.fn(url => Promise.resolve({
|
||||
json: () => Promise.resolve(
|
||||
url.includes('cross-reference')
|
||||
? { pc: { ids: { igdb: 1, screenscraper: 123 }, variables: { screenscraper: { region: 'us' } } } }
|
||||
: { '1': { id: 1, name: 'PC', url: 'https://igdb.com', summary: 'Personal computer.' } },
|
||||
),
|
||||
}));
|
||||
|
||||
await initializePlatformCards();
|
||||
|
||||
expect(globalThis.fetch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'http://localhost/GameDB/platforms/cross-reference.json',
|
||||
{ cache: 'no-store' },
|
||||
);
|
||||
expect(globalThis.fetch).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'http://localhost/GameDB/platforms/all.json',
|
||||
{ cache: 'no-store' },
|
||||
);
|
||||
expect(document.getElementById('platforms-container').textContent).toContain('PC');
|
||||
});
|
||||
});
|
||||
|
||||
describe('run_search', () => {
|
||||
/**
|
||||
* Wait for queued promise callbacks in fetch-driven rendering tests.
|
||||
*
|
||||
* @returns {Promise<void>} Resolves after the current macrotask.
|
||||
*/
|
||||
const flushPromises = () => new Promise(r => setTimeout(r, 0));
|
||||
|
||||
test('does nothing when search term is empty', () => {
|
||||
|
||||
Reference in new issue
Block a user