diff --git a/app/components-react/widgets/ChatBox.tsx b/app/components-react/widgets/ChatBox.tsx index c5076f419574..44313792b1ff 100644 --- a/app/components-react/widgets/ChatBox.tsx +++ b/app/components-react/widgets/ChatBox.tsx @@ -39,7 +39,7 @@ export function ChatBox() { const w = useChatBox(); return ( - {w.hasLoadedSettings() && ( + {!w.state.isLoading && w.settings && ( )} diff --git a/app/components-react/widgets/Credits.tsx b/app/components-react/widgets/Credits.tsx index b68aea014589..62904edfcee9 100644 --- a/app/components-react/widgets/Credits.tsx +++ b/app/components-react/widgets/Credits.tsx @@ -66,13 +66,13 @@ export function Credits() { {$t('Visual Settings')}
- {w.hasLoadedSettings() && w.selectedTab === 'credits' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'credits' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'font' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'font' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'visual' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'visual' && ( )} diff --git a/app/components-react/widgets/DonationTicker.tsx b/app/components-react/widgets/DonationTicker.tsx index be58ec520ee7..29e51f34d540 100644 --- a/app/components-react/widgets/DonationTicker.tsx +++ b/app/components-react/widgets/DonationTicker.tsx @@ -35,7 +35,7 @@ export function DonationTicker() { const w = useDonationTicker(); return ( - {w.hasLoadedSettings() && ( + {!w.state.isLoading && w.settings && ( <> - {w.hasLoadedSettings() && ( + {!w.state.isLoading && w.settings && ( )} diff --git a/app/components-react/widgets/EventList.tsx b/app/components-react/widgets/EventList.tsx index 297c7e9063cd..12e73496895a 100644 --- a/app/components-react/widgets/EventList.tsx +++ b/app/components-react/widgets/EventList.tsx @@ -65,13 +65,13 @@ export function EventList() { {$t('Visual Settings')}
- {w.hasLoadedSettings() && w.selectedTab === 'event' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'event' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'font' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'font' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'visual' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'visual' && ( )} diff --git a/app/components-react/widgets/GenericGoal.tsx b/app/components-react/widgets/GenericGoal.tsx index ed496d61cfae..6f433f5941f8 100644 --- a/app/components-react/widgets/GenericGoal.tsx +++ b/app/components-react/widgets/GenericGoal.tsx @@ -75,7 +75,7 @@ export function GenericGoal() { {!isCharity && {$t('Goal Settings')}}
- {w.hasLoadedSettings() && w.selectedTab === 'goal' && !hasGoal && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'goal' && !hasGoal && ( <> )} - {w.hasLoadedSettings() && w.selectedTab === 'goal' && hasGoal && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'goal' && hasGoal && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'general' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'general' && ( {$t('Images')} - {w.hasLoadedSettings() && w.selectedTab === 'jar' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'jar' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'font' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'font' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'jar-image' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'jar-image' && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'images' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'images' && ( { get jarMeta() { // Check that settings are loaded first, otherwise querying types will throw an error. - if (!this.hasLoadedSettings()) return {}; + if (this.state.isLoading || !this.settings) return {}; return fromMeta({ _enabled_events: { @@ -238,7 +238,7 @@ export class JarModule extends WidgetModule { result[`${type}_image_src`] = metadata.any({ type: 'mediaurl', label }); }); - if (this.hasLoadedSettings()) { + if (!this.state.isLoading && this.settings) { this.settings.types.tips.tiers.forEach((tier, tierIdx) => { const label = $t('Tips over', { amount: tier.minimum_amount }); result[`tips_tier_${tierIdx}_image_src`] = metadata.any({ type: 'mediaurl', label }); diff --git a/app/components-react/widgets/SponsorBanner.tsx b/app/components-react/widgets/SponsorBanner.tsx index 0b7ea372e607..ac29958c199d 100644 --- a/app/components-react/widgets/SponsorBanner.tsx +++ b/app/components-react/widgets/SponsorBanner.tsx @@ -40,9 +40,9 @@ export function SponsorBanner() { const w = useSponsorBanner(); const positions = useMemo(() => { - if (!w.hasLoadedSettings()) return ['1']; + if (w.state.isLoading || !w.settings) return ['1']; return w.settings.placement_options === 'double' ? ['1', '2'] : ['1']; - }, [w.settings, w.hasLoadedSettings]); + }, [w.state.isLoading, w.settings]); return ( @@ -54,14 +54,14 @@ export function SponsorBanner() { {$t('Visual Settings')} - {w.hasLoadedSettings() && w.selectedTab === 'general' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'general' && ( )} - {w.hasLoadedSettings() && ['1', '2'].includes(w.selectedTab) && ( + {!w.state.isLoading && w.settings && ['1', '2'].includes(w.selectedTab) && ( )} - {w.hasLoadedSettings() && w.selectedTab === 'visual' && ( + {!w.state.isLoading && w.settings && w.selectedTab === 'visual' && ( )} diff --git a/app/components-react/widgets/ViewerCount.tsx b/app/components-react/widgets/ViewerCount.tsx index 5f5ddcdc7f83..a798939e6ac5 100644 --- a/app/components-react/widgets/ViewerCount.tsx +++ b/app/components-react/widgets/ViewerCount.tsx @@ -24,7 +24,7 @@ export function ViewerCount() { // use 1 column layout return ( - {w.hasLoadedSettings() && ( + {!w.state.isLoading && w.settings && ( <> diff --git a/app/components-react/widgets/common/useWidget.tsx b/app/components-react/widgets/common/useWidget.tsx index 7232cc38749c..12da90ffb0f5 100644 --- a/app/components-react/widgets/common/useWidget.tsx +++ b/app/components-react/widgets/common/useWidget.tsx @@ -167,18 +167,6 @@ export class WidgetModule { return this.widgetState.data; } - /** - * Checks if the widget has loaded settings, and narrows the type of `this.settings` accordingly. - * - * NOTE: Since the type is narrowed via the `this` value, the static analysis will not work with - * object destructuring! Make sure to keep/use a reference to the module instance. - */ - hasLoadedSettings(): this is this & { - settings: TWidgetState['data']['settings']; - } { - return !!this.settings && !this.state.isLoading; - } - /** * returns widget's settings from the store */ diff --git a/app/components-react/widgets/game-pulse/useGamePulseWidget.tsx b/app/components-react/widgets/game-pulse/useGamePulseWidget.tsx index cdb7f8afeb06..559701f32218 100644 --- a/app/components-react/widgets/game-pulse/useGamePulseWidget.tsx +++ b/app/components-react/widgets/game-pulse/useGamePulseWidget.tsx @@ -178,7 +178,7 @@ export class GamePulseModule extends WidgetModule { scopeId: string, updater: (trigger: GamePulseTrigger) => GamePulseTrigger, ) { - if (!this.hasLoadedSettings()) return; + if (this.state.isLoading || !this.settings) return; const isGlobal = scopeId === ScopeId.Global; const groupSettings = this.getScope(scopeId) || { enabled: true, triggers: [] }; @@ -326,7 +326,7 @@ export class GamePulseModule extends WidgetModule { /** Enable all trigger groups at once. */ public enableAllGroups() { - if (!this.hasLoadedSettings()) return; + if (this.state.isLoading || !this.settings) return; const games = this.settings.games || {}; const newGames: Record = {}; @@ -353,7 +353,7 @@ export class GamePulseModule extends WidgetModule { /** Disable all trigger groups at once. */ public disableAllGroups() { - if (!this.hasLoadedSettings()) return; + if (this.state.isLoading || !this.settings) return; const games = this.settings.games || {}; const newGames: Record = {}; @@ -398,7 +398,7 @@ export class GamePulseModule extends WidgetModule { @Bind() @Throttle(1000) public async testGamePulseTrigger(trigger: GamePulseTrigger) { - if (!this.hasLoadedSettings()) return; + if (this.state.isLoading || !this.settings) return; if (!this.settings.is_muted) { /** @@ -488,7 +488,7 @@ export class GamePulseModule extends WidgetModule { name: string; triggerType: TriggerType; }) { - if (!this.hasLoadedSettings()) return; + if (this.state.isLoading || !this.settings) return; const newTrigger = buildNewTrigger({ triggerType, diff --git a/test/regular/widgets/goals.ts b/test/regular/widgets/goals.ts index 76cfc5627b3e..366258fc9d0c 100644 --- a/test/regular/widgets/goals.ts +++ b/test/regular/widgets/goals.ts @@ -3,7 +3,11 @@ import { addSource } from '../../helpers/modules/sources'; import { logIn } from '../../helpers/webdriver/user'; import { waitForWidgetSettingsSync } from '../../helpers/widget-helpers'; import { assertFormContains, fillForm, useForm } from '../../helpers/modules/forms'; +import { closeWindow } from '../../helpers/modules/core'; +import { platform } from 'os'; +// not a react hook +// eslint-disable-next-line react-hooks/rules-of-hooks useWebdriver(); testGoal('Tip Goal'); @@ -73,6 +77,9 @@ function testGoal(goalType: string) { await fillForm('visualSettingsForm', testSet2); await waitForWidgetSettingsSync(t); await assertFormContains(testSet2); + if (platform() !== 'darwin') { + await closeWindow('child'); + } t.pass(); });