From 3e26da493f68ff5ea9ca7f6da63a8648d2f7bd0b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 17 Aug 2023 12:22:37 -0230 Subject: [PATCH] Initialize composable observable store after update (#20468) * Initialize composable observable store after update The composable observable store now updates state immediately when the structure is updated. Previously each store would only be updated after the first state change. This ensures that the composable observable store state is always complete. * SUpport falsy controller state We now use the nullish coalescing operator when checkint store.state, so that we don't accidentally ignore falsy state. Co-authored-by: Frederik Bolding * Add test for falsy controller state * Update state snapshots A change on `develop` required another state update. --------- Co-authored-by: Frederik Bolding --- app/scripts/lib/ComposableObservableStore.js | 4 + .../lib/ComposableObservableStore.test.js | 40 ++++++++++ ...rs-after-init-opt-in-background-state.json | 80 +++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js index ff722a82d..80d9c483d 100644 --- a/app/scripts/lib/ComposableObservableStore.js +++ b/app/scripts/lib/ComposableObservableStore.js @@ -51,6 +51,7 @@ export default class ComposableObservableStore extends ObservableStore { updateStructure(config) { this.config = config; this.removeAllListeners(); + const initialState = {}; for (const key of Object.keys(config)) { if (!config[key]) { throw new Error(`Undefined '${key}'`); @@ -72,7 +73,10 @@ export default class ComposableObservableStore extends ObservableStore { }, ); } + + initialState[key] = store.state ?? store.getState?.(); } + this.updateState(initialState); } /** diff --git a/app/scripts/lib/ComposableObservableStore.test.js b/app/scripts/lib/ComposableObservableStore.test.js index b6a58f1e1..bb3dd48fd 100644 --- a/app/scripts/lib/ComposableObservableStore.test.js +++ b/app/scripts/lib/ComposableObservableStore.test.js @@ -120,6 +120,46 @@ describe('ComposableObservableStore', () => { }); }); + it('should initialize state with all three types of stores', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + const exampleController = new ExampleController({ + messenger: controllerMessenger, + }); + const oldExampleController = new OldExampleController(); + exampleStore.putState('state'); + exampleController.updateBar('state'); + oldExampleController.updateBaz('state'); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleController, + OldExample: oldExampleController, + Store: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: { bar: 'state' }, + OldExample: { baz: 'state' }, + Store: 'state', + }); + }); + + it('should initialize falsy state', () => { + const controllerMessenger = new ControllerMessenger(); + const exampleStore = new ObservableStore(); + exampleStore.putState(false); + const store = new ComposableObservableStore({ controllerMessenger }); + + store.updateStructure({ + Example: exampleStore, + }); + + expect(store.getState()).toStrictEqual({ + Example: false, + }); + }); + it('should return flattened state', () => { const controllerMessenger = new ControllerMessenger(); const fooStore = new ObservableStore({ foo: 'foo' }); diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index 2f29cc98c..583be2020 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,5 +1,18 @@ { "AccountTracker": { "accounts": "object" }, + "AddressBookController": "object", + "AlertController": { + "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object" + }, + "AnnouncementController": "object", + "AppMetadataController": { + "currentAppVersion": "10.34.4", + "previousAppVersion": "", + "previousMigrationVersion": 0, + "currentMigrationVersion": 94 + }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, @@ -24,6 +37,7 @@ }, "ApprovalController": "object", "CachedBalancesController": "object", + "CronjobController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, @@ -42,6 +56,22 @@ "unapprovedEncryptionPublicKeyMsgCount": 0 }, "EnsController": "object", + "GasFeeController": "object", + "IncomingTransactionsController": { + "incomingTransactions": "object", + "incomingTxLastFetchedBlockByChainId": { + "0x1": null, + "0xe708": null, + "0x5": null, + "0xaa36a7": null, + "0xe704": null + } + }, + "KeyringController": { + "isUnlocked": false, + "keyringTypes": "object", + "keyrings": "object" + }, "MetaMetricsController": { "participateInMetaMetrics": true, "metaMetricsId": "fake-metrics-id", @@ -66,6 +96,51 @@ "networksMetadata": "object", "networkConfigurations": "object" }, + "NftController": "object", + "NotificationController": "object", + "OnboardingController": { + "seedPhraseBackedUp": true, + "firstTimeFlowType": "import", + "completedOnboarding": true, + "onboardingTabs": "object" + }, + "PermissionController": "object", + "PermissionLogController": "object", + "PreferencesController": { + "useBlockie": false, + "useNonceField": false, + "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "use4ByteResolution": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "featureFlags": { "showIncomingTransactions": true }, + "knownMethodData": "object", + "currentLocale": "en", + "identities": "object", + "lostIdentities": "object", + "forgottenPassword": false, + "preferences": { + "hideZeroBalanceTokens": false, + "showFiatInTestnets": false, + "showTestNetworks": false, + "useNativeCurrencyAsPrimaryCurrency": true + }, + "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string" + }, "SignatureController": { "unapprovedMsgs": "object", "unapprovedPersonalMsgs": "object", @@ -74,7 +149,12 @@ "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 }, + "SmartTransactionsController": "object", + "SnapController": "object", + "SnapsRegistry": "object", + "SubjectMetadataController": "object", "SwapsController": "object", + "TokenListController": "object", "TokenRatesController": "object", "TokensController": "object", "TxController": "object"