Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import {
export function resolveInstalledData(state) {
const storeState = getStoreState(state.storeRef)

if (isRecord(state.installedAppsService?.installedApps)) {
// A populated slice is required, not merely a present one: the service starts with
// an empty installedApps object and only fills it in refreshApps. Accepting {} here
// would take this branch with nothing to match and skip the store fallback below.
if (isNonEmptyRecord(state.installedAppsService?.installedApps)) {
return {
rawInstalledApps: state.installedAppsService.installedApps,
catalog: isRecord(state.installedAppsService.catalog)
Expand Down Expand Up @@ -351,6 +354,10 @@ export function getInstalledVersionsForGame(gameId, data) {
)
}

function isNonEmptyRecord(value) {
return isRecord(value) && Object.keys(value).length > 0
}

function getStoreState(storeRef) {
const state =
typeof storeRef?.state?.getValue === "function"
Expand Down Expand Up @@ -561,17 +568,19 @@ function pickImageUrlForTitle(title, game, preferredApp, sidebarClientIconUrl, v
: [title, game, preferredApp]

return pickImageUrl(
// The Wand client icon CDN wins whenever a Steam AppID is resolvable, on any
// install platform. It yields null when no AppID is found, so the metadata
// candidates below still apply to non-Steam titles.
getSteamClientIconUrl(
findSteamAppId(...steamRoots),
getInstalledAppSteamAppId(preferredApp.platform, preferredApp.sku)
),
title?.imageUrl,
title?.iconUrl,
title?.coverUrl,
title?.thumbnailUrl,
title?.logoUrl,
title?.headerImageUrl,
getSteamClientIconUrl(
findSteamAppId(...steamRoots),
getInstalledAppSteamAppId(preferredApp.platform, preferredApp.sku)
),
sidebarClientIconUrl,
title?.images,
title?.assets,
game.imageUrl,
Expand All @@ -582,6 +591,9 @@ function pickImageUrlForTitle(title, game, preferredApp, sidebarClientIconUrl, v
game.headerImageUrl,
game.images,
game.assets,
preferredApp.imageUrl
preferredApp.imageUrl,
// Scraped from the rendered sidebar, so it is the last resort once no
// metadata source exposed artwork.
sidebarClientIconUrl
)
}
87 changes: 87 additions & 0 deletions web-panel/bridge/src/renderer-scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,45 @@ import {
getSteamClientIconUrl,
normalizeImageUrl,
} from '../scripts/default/installed-apps-sync/artwork.js';
import {
buildSnapshot,
resolveInstalledData,
} from '../scripts/default/installed-apps-sync/installed-data.js';
import { resolveQrRenderer } from '../scripts/default/remote-popup-cleanup/qr-renderer.js';

const CLIENT_ICON_URL =
'https://api-cdn.wemod.com/steam_community/1245620/client_icon/96.webp';

function makeInstalledAppsState(
correlationId: string,
app: Record<string, unknown>,
title: Record<string, unknown> = {}
) {
return {
log: () => undefined,
storeRef: null,
unavailableTitlesById: {},
installedAppsService: {
installedApps: { [correlationId]: app },
catalog: {
games: {
'900': {
titleId: '500',
correlationIds: [correlationId],
displayName: 'Subnautica',
},
},
titles: {
'500': { name: 'Subnautica', ...title },
},
},
installedVersions: {
'900': [{ correlationId }],
},
},
};
}

describe('installed-apps renderer script models', () => {
it('normalizes captured artwork shapes without a Wand runtime', () => {
expect(normalizeImageUrl({ cover: { imageUrl: '//cdn.example/game.webp' } }))
Expand All @@ -30,6 +67,56 @@ describe('installed-apps renderer script models', () => {
.toBe('https://api-cdn.wemod.com/steam_community/1245620/client_icon/96.webp');
});

it('prefers the Steam client icon over the title\'s own artwork fields', () => {
const snapshot = buildSnapshot(
makeInstalledAppsState(
'steam:1245620',
{ platform: 'steam', sku: '1245620', displayName: 'Subnautica' },
{
imageUrl: 'https://cdn.example/subnautica-cover.webp',
coverUrl: 'https://cdn.example/subnautica-alt.webp',
}
)
);

expect(snapshot?.apps).toHaveLength(1);
expect(snapshot?.apps[0].imageUrl).toBe(CLIENT_ICON_URL);
});

it('falls back to title artwork when no Steam AppID is resolvable', () => {
const snapshot = buildSnapshot(
makeInstalledAppsState(
'epic:abc',
{ platform: 'epic', sku: 'abc', displayName: 'Subnautica' },
{ imageUrl: 'https://cdn.example/epic-cover.webp' }
)
);

expect(snapshot?.apps[0].imageUrl).toBe('https://cdn.example/epic-cover.webp');
});

it('reads installed apps from the store while the service slice is still empty', () => {
const storeInstalledApps = {
'steam:1245620': { platform: 'steam', sku: '1245620' },
};
const data = resolveInstalledData({
unavailableTitlesById: {},
installedAppsService: { installedApps: {} },
storeRef: {
state: {
getValue: () => ({
installedApps: storeInstalledApps,
catalog: {},
installedGameVersions: {},
}),
},
},
});

expect(data?.source).toBe('store');
expect(data?.rawInstalledApps).toBe(storeInstalledApps);
});

it('resolves the tree-shaken Wand QR renderer without a create export', () => {
const renderer = () => undefined;
const webpackRequire = {
Expand Down