Skip to content
Merged
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
19 changes: 15 additions & 4 deletions packages/app/src/context/SettingsContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ const VALUE_CONVERSIONS = {
toServer: v => v ? 'always' : 'never',
fromServer: v => v !== 'never'
},
// The other clients offer off, logo or library, where this app keeps a separate toggle for
// turning it off. There is nothing to draw for "off", so leave the mode we already have.
screensaverMode: {
fromServer: v => (v === 'off' ? undefined : v)
},
// Three states here against a boolean elsewhere. "Per Library" has no equivalent, so it
// declines to push and leaves whatever the server holds.
folderViewMode: {
Expand All @@ -137,7 +142,7 @@ const VALUE_CONVERSIONS = {
// move together, so it gets resolved whole rather than a key at a time.
};

const SYNCABLE_KEYS = [
export const SYNCABLE_KEYS = [
'showShuffleButton', 'shuffleContentType', 'showGenresButton',
'showFavoritesButton', 'showLibrariesInToolbar', 'mergeContinueWatchingNextUp',
'nextUpMaxDays',
Expand Down Expand Up @@ -177,18 +182,24 @@ const SYNCABLE_KEYS = [
'diagnosticLoggingEnabled',
'uiLanguage',
'blockedRatings',
'customHomeRows',
'mergeRadarrSonarrCalendars',
'radarrCalendarShowCinema', 'radarrCalendarShowDigital', 'radarrCalendarShowPhysical',
'radarrCalendarShowDate', 'sonarrCalendarShowDate', 'sonarrCalendarShowEpisodeInfo',
'showSeerrButton',
'screensaverMode',
// Settings this app has no screen for. They ride along so a value set on another client
// survives the profile the TV writes back.
'showCastButton', 'themeMusicLoop',
'classicHomeRowsPadding', 'modernHomeRowsPadding',
'detailShowTechnicalDetails',
'recommendationSystemSource', 'recommendationsApplyParentalRatingCap',
'detailButtonOrderTv', 'hiddenDetailButtonsTv', 'osdButtonOrderTv', 'hiddenOsdButtonsTv',
'focusBorderColor',
'navbarOpacity',
'navbarColor',
];

const profileToLocal = (serverProfile) => {
export const profileToLocal = (serverProfile) => {
if (!serverProfile) return {};
const local = {};
for (const [key, value] of Object.entries(serverProfile)) {
Expand All @@ -211,7 +222,7 @@ const profileToLocal = (serverProfile) => {
return local;
};

const localToProfile = (localSettings) => {
export const localToProfile = (localSettings) => {
const profile = {};
for (const key of SYNCABLE_KEYS) {
if (key === 'homeRows') continue;
Expand Down
9 changes: 9 additions & 0 deletions packages/app/src/context/defaultSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ export const defaultSettings = {
truehdPassthrough: true,
blockedRatings: [],
showSeerrButton: true,
// Synced but not shown anywhere in this app, so null keeps the TV from stamping a default
// of its own over what another client set. The padding pair is a pixel count on the server.
themeMusicLoop: null,
showCastButton: null,
classicHomeRowsPadding: null,
modernHomeRowsPadding: null,
detailShowTechnicalDetails: null,
recommendationSystemSource: null,
recommendationsApplyParentalRatingCap: null,
performanceMode: 'auto',
focusBorderColor: '',
navbarOpacity: 100,
Expand Down
84 changes: 84 additions & 0 deletions packages/app/src/context/settingsSync.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// The server profile is typed, so a key this app spells differently isn't refused, it's
// quietly dropped. Nothing on this side can prove a field name is right, but these lock down
// what gets sent and what gets taken.

// Nothing renders here. The provider only needs React to exist while the module loads, and
// the real one can't be pulled in because the CLI ships a second copy that disagrees with it.
jest.mock('react/jsx-dev-runtime', () => ({}));
jest.mock('react', () => ({createContext: () => ({})}));
// Storage picks its platform module through a dynamic import that jest can't transform.
jest.mock('../services/storage', () => ({}));

import {SYNCABLE_KEYS, defaultSettings, profileToLocal, localToProfile} from './SettingsContext';

describe('profileToLocal', () => {
test('takes the TV button fields under their own names', () => {
const local = profileToLocal({
detailButtonOrderTv: ['play', 'trailer'],
hiddenDetailButtonsTv: ['shuffle'],
osdButtonOrderTv: ['subtitles'],
hiddenOsdButtonsTv: ['audio']
});

expect(local.detailButtonOrderTv).toEqual(['play', 'trailer']);
expect(local.hiddenDetailButtonsTv).toEqual(['shuffle']);
expect(local.osdButtonOrderTv).toEqual(['subtitles']);
expect(local.hiddenOsdButtonsTv).toEqual(['audio']);
});

test('leaves the desktop and mobile button fields alone', () => {
const local = profileToLocal({
osdButtonOrderDesktop: ['desktop-order'],
hiddenDetailButtonsMobile: ['mobile-hidden']
});

expect(local).toEqual({});
});

test('ignores a screensaver mode this app has no way to draw', () => {
expect(profileToLocal({screensaverMode: 'off'}).screensaverMode).toBeUndefined();
expect(profileToLocal({screensaverMode: 'logo'}).screensaverMode).toBe('logo');
});
});

describe('localToProfile', () => {
test('says nothing about settings this app has no screen for', () => {
const profile = localToProfile(defaultSettings);

for (const key of ['showCastButton', 'themeMusicLoop', 'classicHomeRowsPadding',
'modernHomeRowsPadding', 'detailShowTechnicalDetails', 'recommendationSystemSource',
'recommendationsApplyParentalRatingCap']) {
expect(profile).not.toHaveProperty(key);
}
});

test('writes those settings back once the server has supplied one', () => {
const profile = localToProfile({...defaultSettings, ...profileToLocal({
showCastButton: false,
classicHomeRowsPadding: 12
})});

expect(profile.showCastButton).toBe(false);
expect(profile.classicHomeRowsPadding).toBe(12);
});

test('keeps the local only home row list out of the profile', () => {
const profile = localToProfile({...defaultSettings, customHomeRows: [{id: 'row'}]});

expect(profile).not.toHaveProperty('customHomeRows');
});

// Some synced keys have no default at all, which is how a screen asks for its built in
// order rather than a stored one.
test('invents nothing when there is no local value to send', () => {
expect(localToProfile({})).toEqual({});
});
});

describe('SYNCABLE_KEYS', () => {
test('no key is listed twice', () => {
const repeated = SYNCABLE_KEYS.filter((key, i) => SYNCABLE_KEYS.indexOf(key) !== i);

expect(repeated).toEqual([]);
});
});
Loading