From 4cf886f710c5cb16e5dc9d0504330bf404594307 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"