refactor: offline roles (#2169)
This commit is contained in:
@@ -1160,8 +1160,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.apiService.isThirdPartyHostUsed()
|
||||
}
|
||||
|
||||
public getCloudProviderIntegrationUrl(cloudProviderName: Settings.CloudProvider, isDevEnvironment: boolean): string {
|
||||
return this.settingsService.getCloudProviderIntegrationUrl(cloudProviderName, isDevEnvironment)
|
||||
public getCloudProviderIntegrationUrl(cloudProviderName: Settings.CloudProvider): string {
|
||||
return this.settingsService.getCloudProviderIntegrationUrl(cloudProviderName)
|
||||
}
|
||||
|
||||
private constructServices() {
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
export const APPLICATION_DEFAULT_HOSTS = [
|
||||
'api.standardnotes.com',
|
||||
'api-dev.standardnotes.com',
|
||||
'sync.standardnotes.org',
|
||||
'syncing-server-demo.standardnotes.com',
|
||||
]
|
||||
export const APPLICATION_DEFAULT_HOSTS = ['api.standardnotes.com', 'sync.standardnotes.org']
|
||||
|
||||
export const FILES_DEFAULT_HOSTS = ['files.standardnotes.com', 'files-dev.standardnotes.com']
|
||||
export const FILES_DEFAULT_HOSTS = ['files.standardnotes.com']
|
||||
|
||||
export const TRUSTED_FEATURE_HOSTS = [
|
||||
'api-dev.standardnotes.com',
|
||||
'api.standardnotes.com',
|
||||
'extensions.standardnotes.com',
|
||||
'extensions.standardnotes.org',
|
||||
'extensions-server-dev.standardnotes.org',
|
||||
'extensions-server-dev.standardnotes.com',
|
||||
'features.standardnotes.com',
|
||||
]
|
||||
|
||||
export enum ExtensionsServerURL {
|
||||
Dev = 'https://extensions-server-dev.standardnotes.org',
|
||||
Prod = 'https://extensions.standardnotes.org',
|
||||
}
|
||||
|
||||
|
||||
@@ -654,7 +654,7 @@ export class SNApiService
|
||||
|
||||
public async downloadOfflineFeaturesFromRepo(
|
||||
repo: SNFeatureRepo,
|
||||
): Promise<{ features: FeatureDescription[] } | ClientDisplayableError> {
|
||||
): Promise<{ features: FeatureDescription[]; roles: string[] } | ClientDisplayableError> {
|
||||
try {
|
||||
const featuresUrl = repo.offlineFeaturesUrl
|
||||
const extensionKey = repo.offlineKey
|
||||
@@ -678,8 +678,10 @@ export class SNApiService
|
||||
if (response.error) {
|
||||
return ClientDisplayableError.FromError(response.error)
|
||||
}
|
||||
const data = (response as Responses.GetOfflineFeaturesResponse).data
|
||||
return {
|
||||
features: (response as Responses.GetOfflineFeaturesResponse).data?.features || [],
|
||||
features: data?.features || [],
|
||||
roles: data?.roles || [],
|
||||
}
|
||||
} catch {
|
||||
return new ClientDisplayableError(API_MESSAGE_FAILED_OFFLINE_ACTIVATION)
|
||||
|
||||
@@ -80,7 +80,7 @@ export class SNWebSocketsService extends AbstractService<WebSocketsServiceEvent,
|
||||
|
||||
return response.data.token
|
||||
} catch (error) {
|
||||
console.error((error as Error).message)
|
||||
console.error('Caught error:', (error as Error).message)
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('featuresService', () => {
|
||||
featuresService.getExperimentalFeatures = jest.fn().mockReturnValue([FeatureIdentifier.PlusEditor])
|
||||
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -191,7 +191,7 @@ describe('featuresService', () => {
|
||||
featuresService.getEnabledExperimentalFeatures = jest.fn().mockReturnValue([FeatureIdentifier.PlusEditor])
|
||||
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -213,7 +213,7 @@ describe('featuresService', () => {
|
||||
const mock = (featuresService['notifyEvent'] = jest.fn())
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.PlusUser]
|
||||
await featuresService.setRoles(newRoles)
|
||||
await featuresService.setOnlineRoles(newRoles)
|
||||
|
||||
expect(mock.mock.calls[0][0]).toEqual(FeaturesEvent.UserRolesChanged)
|
||||
})
|
||||
@@ -226,7 +226,7 @@ describe('featuresService', () => {
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
|
||||
expect(spy.mock.calls[2][0]).toEqual(FeaturesEvent.DidPurchaseSubscription)
|
||||
})
|
||||
@@ -235,12 +235,12 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
featuresService['roles'] = []
|
||||
featuresService['onlineRoles'] = []
|
||||
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.NAMES.ProUser]
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
|
||||
const triggeredEvents = spy.mock.calls.map((call) => call[0])
|
||||
expect(triggeredEvents).not.toContain(FeaturesEvent.DidPurchaseSubscription)
|
||||
@@ -252,7 +252,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||
expect(apiService.getUserFeatures).toHaveBeenCalledWith('123')
|
||||
})
|
||||
@@ -263,7 +263,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserRoles, newRoles)
|
||||
expect(apiService.getUserFeatures).toHaveBeenCalledWith('123')
|
||||
})
|
||||
@@ -274,7 +274,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(storageService.setValue).toHaveBeenCalledWith(StorageKey.UserFeatures, features)
|
||||
})
|
||||
|
||||
@@ -284,7 +284,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).toHaveBeenCalledTimes(2)
|
||||
expect(itemManager.createItem).toHaveBeenCalledWith(
|
||||
ContentType.Theme,
|
||||
@@ -328,7 +328,7 @@ describe('featuresService', () => {
|
||||
itemManager.getItems = jest.fn().mockReturnValue([existingItem])
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
|
||||
expect(itemManager.changeComponent).toHaveBeenCalledWith(existingItem, expect.any(Function))
|
||||
})
|
||||
@@ -354,7 +354,7 @@ describe('featuresService', () => {
|
||||
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).toHaveBeenCalledWith(
|
||||
ContentType.Component,
|
||||
expect.objectContaining({
|
||||
@@ -401,7 +401,7 @@ describe('featuresService', () => {
|
||||
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.setItemsToBeDeleted).toHaveBeenCalledWith([existingItem])
|
||||
})
|
||||
|
||||
@@ -424,7 +424,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -447,7 +447,7 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(itemManager.createItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -455,10 +455,10 @@ describe('featuresService', () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', roles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', roles)
|
||||
expect(storageService.setValue).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
@@ -482,7 +482,7 @@ describe('featuresService', () => {
|
||||
const nativeFeature = featuresService['mapRemoteNativeFeatureToStaticFeature'](remoteFeature)
|
||||
featuresService['mapRemoteNativeFeatureToItem'] = jest.fn()
|
||||
featuresService.initializeFromDisk()
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', newRoles)
|
||||
expect(featuresService['mapRemoteNativeFeatureToItem']).toHaveBeenCalledWith(
|
||||
nativeFeature,
|
||||
expect.anything(),
|
||||
@@ -509,7 +509,26 @@ describe('featuresService', () => {
|
||||
await expect(() => featuresService['mapRemoteNativeFeatureToItem'](clientFeature, [], [])).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('feature status', async () => {
|
||||
it('role-based feature status', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
features = [] as jest.Mocked<FeatureDescription[]>
|
||||
|
||||
apiService.getUserFeatures = jest.fn().mockReturnValue({
|
||||
data: {
|
||||
features,
|
||||
},
|
||||
})
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('feature status with no paid role but features listings', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
features = [
|
||||
@@ -535,54 +554,21 @@ describe('featuresService', () => {
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.PlusEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.PlusEditor)).toBe(FeatureStatus.NoUserSubscription)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NoUserSubscription)
|
||||
|
||||
features = [
|
||||
{
|
||||
identifier: FeatureIdentifier.MidnightTheme,
|
||||
content_type: ContentType.Theme,
|
||||
expires_at: expiredDate,
|
||||
role_name: RoleName.NAMES.PlusUser,
|
||||
},
|
||||
{
|
||||
identifier: FeatureIdentifier.PlusEditor,
|
||||
content_type: ContentType.Component,
|
||||
expires_at: expiredDate,
|
||||
role_name: RoleName.NAMES.ProUser,
|
||||
},
|
||||
] as jest.Mocked<FeatureDescription[]>
|
||||
|
||||
apiService.getUserFeatures = jest.fn().mockReturnValue({
|
||||
data: {
|
||||
features,
|
||||
},
|
||||
})
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(
|
||||
FeatureStatus.InCurrentPlanButExpired,
|
||||
)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.PlusEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SheetsEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('availableInRoles-based features', async () => {
|
||||
it('role-based features while not signed into first party server', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser])
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.Entitled)
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.SuperEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('third party feature status', async () => {
|
||||
@@ -629,7 +615,7 @@ describe('featuresService', () => {
|
||||
} as never),
|
||||
])
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(themeFeature.identifier)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(editorFeature.identifier)).toBe(FeatureStatus.InCurrentPlanButExpired)
|
||||
@@ -641,7 +627,7 @@ describe('featuresService', () => {
|
||||
it('feature status should be not entitled if no account or offline repo', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
@@ -653,30 +639,6 @@ describe('featuresService', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('feature status should be entitled for subscriber until first successful features request made if no cached features', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
apiService.getUserFeatures = jest.fn().mockReturnValue({
|
||||
data: {
|
||||
features: [],
|
||||
},
|
||||
})
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
featuresService['completedSuccessfulFeaturesRetrieval'] = false
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.Entitled)
|
||||
|
||||
await featuresService.didDownloadFeatures(features)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('didDownloadFeatures should filter out client controlled features', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
@@ -687,31 +649,13 @@ describe('featuresService', () => {
|
||||
expect(featuresService['mapRemoteNativeFeaturesToItems']).toHaveBeenCalledWith([])
|
||||
})
|
||||
|
||||
it('feature status should be dynamic for subscriber if cached features and no successful features request made yet', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
featuresService['completedSuccessfulFeaturesRetrieval'] = false
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
|
||||
featuresService['completedSuccessfulFeaturesRetrieval'] = false
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
})
|
||||
|
||||
it('feature status for offline subscription', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
featuresService.rolesIncludePaidSubscription = jest.fn().mockReturnValue(false)
|
||||
featuresService.onlineRolesIncludePaidSubscription = jest.fn().mockReturnValue(false)
|
||||
featuresService['completedSuccessfulFeaturesRetrieval'] = true
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
||||
@@ -720,9 +664,11 @@ describe('featuresService', () => {
|
||||
)
|
||||
|
||||
featuresService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||
featuresService.hasFirstPartySubscription = jest.fn().mockReturnValue(true)
|
||||
await featuresService.setOfflineRoles([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.Entitled)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.NotInCurrentPlan)
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.TokenVaultEditor)).toBe(FeatureStatus.Entitled)
|
||||
})
|
||||
|
||||
it('feature status for deprecated feature', async () => {
|
||||
@@ -734,7 +680,7 @@ describe('featuresService', () => {
|
||||
FeatureStatus.NoUserSubscription,
|
||||
)
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.DeprecatedFileSafe as FeatureIdentifier)).toBe(
|
||||
FeatureStatus.Entitled,
|
||||
@@ -744,25 +690,25 @@ describe('featuresService', () => {
|
||||
it('has paid subscription', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(featuresService.hasPaidOnlineOrOfflineSubscription()).toBeFalsy
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toBeFalsy
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
expect(featuresService.hasPaidOnlineOrOfflineSubscription()).toEqual(true)
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
|
||||
it('has paid subscription should be true if offline repo and signed into third party server', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
|
||||
featuresService.hasOfflineRepo = jest.fn().mockReturnValue(true)
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(featuresService.hasPaidOnlineOrOfflineSubscription()).toEqual(true)
|
||||
expect(featuresService.hasPaidAnyPartyOnlineOrOfflineSubscription()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -829,7 +775,11 @@ describe('featuresService', () => {
|
||||
it('should sort given roles according to role hierarchy', () => {
|
||||
const featuresService = createService()
|
||||
|
||||
const sortedRoles = featuresService.rolesBySorting([RoleName.NAMES.ProUser, RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser])
|
||||
const sortedRoles = featuresService.rolesBySorting([
|
||||
RoleName.NAMES.ProUser,
|
||||
RoleName.NAMES.CoreUser,
|
||||
RoleName.NAMES.PlusUser,
|
||||
])
|
||||
|
||||
expect(sortedRoles).toStrictEqual([RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser])
|
||||
})
|
||||
@@ -839,7 +789,7 @@ describe('featuresService', () => {
|
||||
it('should be false if core user checks for plus role', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.CoreUser])
|
||||
|
||||
const hasPlusUserRole = featuresService.hasMinimumRole(RoleName.NAMES.PlusUser)
|
||||
|
||||
@@ -849,7 +799,9 @@ describe('featuresService', () => {
|
||||
it('should be false if plus user checks for pro role', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.PlusUser, RoleName.NAMES.CoreUser])
|
||||
|
||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
|
||||
@@ -859,7 +811,9 @@ describe('featuresService', () => {
|
||||
it('should be true if pro user checks for core user', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
const hasCoreUserRole = featuresService.hasMinimumRole(RoleName.NAMES.CoreUser)
|
||||
|
||||
@@ -869,7 +823,9 @@ describe('featuresService', () => {
|
||||
it('should be true if pro user checks for pro user', async () => {
|
||||
const featuresService = createService()
|
||||
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(true)
|
||||
|
||||
await featuresService.updateOnlineRolesAndFetchFeatures('123', [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser])
|
||||
|
||||
const hasProUserRole = featuresService.hasMinimumRole(RoleName.NAMES.ProUser)
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ export class SNFeaturesService
|
||||
implements FeaturesClientInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private deinited = false
|
||||
private roles: string[] = []
|
||||
private onlineRoles: string[] = []
|
||||
private offlineRoles: string[] = []
|
||||
private features: FeaturesImports.FeatureDescription[] = []
|
||||
private enabledExperimentalFeatures: FeaturesImports.FeatureIdentifier[] = []
|
||||
private removeWebSocketsServiceObserver: () => void
|
||||
@@ -87,7 +88,7 @@ export class SNFeaturesService
|
||||
const {
|
||||
payload: { userUuid, currentRoles },
|
||||
} = data as UserRolesChangedEvent
|
||||
await this.updateRolesAndFetchFeatures(userUuid, currentRoles)
|
||||
await this.updateOnlineRolesAndFetchFeatures(userUuid, currentRoles)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -124,6 +125,16 @@ export class SNFeaturesService
|
||||
})
|
||||
}
|
||||
|
||||
public initializeFromDisk(): void {
|
||||
this.onlineRoles = this.storageService.getValue<string[]>(StorageKey.UserRoles, undefined, [])
|
||||
|
||||
this.offlineRoles = this.storageService.getValue<string[]>(StorageKey.OfflineUserRoles, undefined, [])
|
||||
|
||||
this.features = this.storageService.getValue(StorageKey.UserFeatures, undefined, [])
|
||||
|
||||
this.enabledExperimentalFeatures = this.storageService.getValue(StorageKey.ExperimentalFeatures, undefined, [])
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === ApiServiceEvent.MetaReceived) {
|
||||
if (!this.syncService) {
|
||||
@@ -142,7 +153,7 @@ export class SNFeaturesService
|
||||
}
|
||||
|
||||
const { userUuid, userRoles } = event.payload as MetaReceivedData
|
||||
await this.updateRolesAndFetchFeatures(
|
||||
await this.updateOnlineRolesAndFetchFeatures(
|
||||
userUuid,
|
||||
userRoles.map((role) => role.name),
|
||||
)
|
||||
@@ -155,7 +166,7 @@ export class SNFeaturesService
|
||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
||||
void this.mapClientControlledFeaturesToItems()
|
||||
|
||||
if (!this.rolesIncludePaidSubscription()) {
|
||||
if (!this.hasFirstPartyOnlineSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
if (offlineRepo) {
|
||||
void this.downloadOfflineFeatures(offlineRepo)
|
||||
@@ -194,7 +205,7 @@ export class SNFeaturesService
|
||||
}
|
||||
|
||||
public enableExperimentalFeature(identifier: FeaturesImports.FeatureIdentifier): void {
|
||||
const feature = this.getUserFeature(identifier)
|
||||
const feature = this.getFeatureThatOriginallyCameFromServer(identifier)
|
||||
|
||||
this.enabledExperimentalFeatures.push(identifier)
|
||||
|
||||
@@ -309,10 +320,14 @@ export class SNFeaturesService
|
||||
repo: Models.SNFeatureRepo,
|
||||
): Promise<SetOfflineFeaturesFunctionResponse | ClientDisplayableError> {
|
||||
const result = await this.apiService.downloadOfflineFeaturesFromRepo(repo)
|
||||
|
||||
if (result instanceof ClientDisplayableError) {
|
||||
return result
|
||||
}
|
||||
|
||||
await this.didDownloadFeatures(result.features)
|
||||
await this.setOfflineRoles(result.roles)
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -363,18 +378,29 @@ export class SNFeaturesService
|
||||
}
|
||||
}
|
||||
|
||||
public initializeFromDisk(): void {
|
||||
this.roles = this.storageService.getValue<string[]>(StorageKey.UserRoles, undefined, [])
|
||||
|
||||
this.features = this.storageService.getValue(StorageKey.UserFeatures, undefined, [])
|
||||
|
||||
this.enabledExperimentalFeatures = this.storageService.getValue(StorageKey.ExperimentalFeatures, undefined, [])
|
||||
hasFirstPartyOnlineSubscription(): boolean {
|
||||
return this.sessionManager.isSignedIntoFirstPartyServer() && this.onlineRolesIncludePaidSubscription()
|
||||
}
|
||||
|
||||
public async updateRolesAndFetchFeatures(userUuid: UuidString, roles: string[]): Promise<void> {
|
||||
const previousRoles = this.roles
|
||||
hasFirstPartySubscription(): boolean {
|
||||
if (this.hasFirstPartyOnlineSubscription()) {
|
||||
return true
|
||||
}
|
||||
|
||||
const userRolesChanged = this.haveRolesChanged(roles)
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
if (!offlineRepo) {
|
||||
return false
|
||||
}
|
||||
|
||||
const hasFirstPartyOfflineSubscription = offlineRepo.content.offlineFeaturesUrl === PROD_OFFLINE_FEATURES_URL
|
||||
return hasFirstPartyOfflineSubscription
|
||||
}
|
||||
|
||||
async updateOnlineRolesAndFetchFeatures(userUuid: UuidString, roles: string[]): Promise<void> {
|
||||
const previousRoles = this.onlineRoles
|
||||
|
||||
const userRolesChanged =
|
||||
roles.some((role) => !this.onlineRoles.includes(role)) || this.onlineRoles.some((role) => !roles.includes(role))
|
||||
|
||||
const isInitialLoadRolesChange = previousRoles.length === 0 && userRolesChanged
|
||||
|
||||
@@ -384,7 +410,7 @@ export class SNFeaturesService
|
||||
|
||||
this.needsInitialFeaturesUpdate = false
|
||||
|
||||
await this.setRoles(roles)
|
||||
await this.setOnlineRoles(roles)
|
||||
|
||||
const shouldDownloadRoleBasedFeatures = !this.hasOfflineRepo()
|
||||
|
||||
@@ -398,22 +424,34 @@ export class SNFeaturesService
|
||||
}
|
||||
|
||||
if (userRolesChanged && !isInitialLoadRolesChange) {
|
||||
if (this.rolesIncludePaidSubscription()) {
|
||||
if (this.onlineRolesIncludePaidSubscription()) {
|
||||
await this.notifyEvent(FeaturesEvent.DidPurchaseSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setRoles(roles: string[]): Promise<void> {
|
||||
const rolesChanged = !arraysEqual(this.roles, roles)
|
||||
async setOnlineRoles(roles: string[]): Promise<void> {
|
||||
const rolesChanged = !arraysEqual(this.onlineRoles, roles)
|
||||
|
||||
this.roles = roles
|
||||
this.onlineRoles = roles
|
||||
|
||||
if (rolesChanged) {
|
||||
void this.notifyEvent(FeaturesEvent.UserRolesChanged)
|
||||
}
|
||||
|
||||
this.storageService.setValue(StorageKey.UserRoles, this.roles)
|
||||
this.storageService.setValue(StorageKey.UserRoles, this.onlineRoles)
|
||||
}
|
||||
|
||||
async setOfflineRoles(roles: string[]): Promise<void> {
|
||||
const rolesChanged = !arraysEqual(this.offlineRoles, roles)
|
||||
|
||||
this.offlineRoles = roles
|
||||
|
||||
if (rolesChanged) {
|
||||
void this.notifyEvent(FeaturesEvent.UserRolesChanged)
|
||||
}
|
||||
|
||||
this.storageService.setValue(StorageKey.OfflineUserRoles, this.offlineRoles)
|
||||
}
|
||||
|
||||
public async didDownloadFeatures(features: FeaturesImports.FeatureDescription[]): Promise<void> {
|
||||
@@ -465,17 +503,19 @@ export class SNFeaturesService
|
||||
return nativeFeatureCopy
|
||||
}
|
||||
|
||||
public getUserFeature(featureId: FeaturesImports.FeatureIdentifier): FeaturesImports.FeatureDescription | undefined {
|
||||
public getFeatureThatOriginallyCameFromServer(
|
||||
featureId: FeaturesImports.FeatureIdentifier,
|
||||
): FeaturesImports.FeatureDescription | undefined {
|
||||
return this.features.find((feature) => feature.identifier === featureId)
|
||||
}
|
||||
|
||||
rolesIncludePaidSubscription(): boolean {
|
||||
onlineRolesIncludePaidSubscription(): boolean {
|
||||
const unpaidRoles = [RoleName.NAMES.CoreUser]
|
||||
return this.roles.some((role) => !unpaidRoles.includes(role))
|
||||
return this.onlineRoles.some((role) => !unpaidRoles.includes(role))
|
||||
}
|
||||
|
||||
public hasPaidOnlineOrOfflineSubscription(): boolean {
|
||||
return this.rolesIncludePaidSubscription() || this.hasOfflineRepo()
|
||||
hasPaidAnyPartyOnlineOrOfflineSubscription(): boolean {
|
||||
return this.onlineRolesIncludePaidSubscription() || this.hasOfflineRepo()
|
||||
}
|
||||
|
||||
public rolesBySorting(roles: string[]): string[] {
|
||||
@@ -485,7 +525,7 @@ export class SNFeaturesService
|
||||
public hasMinimumRole(role: string): boolean {
|
||||
const sortedAllRoles = Object.values(RoleName.NAMES)
|
||||
|
||||
const sortedUserRoles = this.rolesBySorting(this.roles)
|
||||
const sortedUserRoles = this.rolesBySorting(this.rolesToUseForFeatureCheck())
|
||||
|
||||
const highestUserRoleIndex = sortedAllRoles.indexOf(lastElement(sortedUserRoles) as string)
|
||||
|
||||
@@ -508,16 +548,10 @@ export class SNFeaturesService
|
||||
}
|
||||
|
||||
const nativeFeature = FeaturesImports.FindNativeFeature(featureId)
|
||||
if (nativeFeature && nativeFeature.availableInRoles) {
|
||||
const hasRole = this.roles.some((role) => nativeFeature.availableInRoles?.includes(role))
|
||||
if (hasRole) {
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
}
|
||||
|
||||
const isDeprecated = this.isFeatureDeprecated(featureId)
|
||||
if (isDeprecated) {
|
||||
if (this.hasPaidOnlineOrOfflineSubscription()) {
|
||||
if (this.hasPaidAnyPartyOnlineOrOfflineSubscription()) {
|
||||
return FeatureStatus.Entitled
|
||||
} else {
|
||||
return FeatureStatus.NoUserSubscription
|
||||
@@ -538,7 +572,7 @@ export class SNFeaturesService
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
if (this.hasPaidOnlineOrOfflineSubscription()) {
|
||||
if (this.hasPaidAnyPartyOnlineOrOfflineSubscription()) {
|
||||
if (!this.completedSuccessfulFeaturesRetrieval) {
|
||||
const hasCachedFeatures = this.features.length > 0
|
||||
const temporarilyAllowUntilServerUpdates = !hasCachedFeatures
|
||||
@@ -550,25 +584,27 @@ export class SNFeaturesService
|
||||
return FeatureStatus.NoUserSubscription
|
||||
}
|
||||
|
||||
const feature = this.getUserFeature(featureId)
|
||||
if (!feature) {
|
||||
return FeatureStatus.NotInCurrentPlan
|
||||
}
|
||||
|
||||
const expired = feature.expires_at && new Date(feature.expires_at).getTime() < new Date().getTime()
|
||||
if (expired) {
|
||||
if (!this.roles.includes(feature.role_name as string)) {
|
||||
if (nativeFeature) {
|
||||
if (!this.hasFirstPartySubscription()) {
|
||||
return FeatureStatus.NotInCurrentPlan
|
||||
} else {
|
||||
return FeatureStatus.InCurrentPlanButExpired
|
||||
}
|
||||
|
||||
const roles = this.rolesToUseForFeatureCheck()
|
||||
if (nativeFeature.availableInRoles) {
|
||||
const hasRole = roles.some((role) => {
|
||||
return nativeFeature.availableInRoles?.includes(role)
|
||||
})
|
||||
if (!hasRole) {
|
||||
return FeatureStatus.NotInCurrentPlan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FeatureStatus.Entitled
|
||||
}
|
||||
|
||||
private haveRolesChanged(roles: string[]): boolean {
|
||||
return roles.some((role) => !this.roles.includes(role)) || this.roles.some((role) => !roles.includes(role))
|
||||
private rolesToUseForFeatureCheck(): string[] {
|
||||
return this.hasFirstPartyOnlineSubscription() ? this.onlineRoles : this.offlineRoles
|
||||
}
|
||||
|
||||
private componentContentForNativeFeatureDescription(feature: FeaturesImports.FeatureDescription): Models.ItemContent {
|
||||
@@ -776,7 +812,8 @@ export class SNFeaturesService
|
||||
;(this.removeWebSocketsServiceObserver as unknown) = undefined
|
||||
this.removefeatureReposObserver()
|
||||
;(this.removefeatureReposObserver as unknown) = undefined
|
||||
;(this.roles as unknown) = undefined
|
||||
;(this.onlineRoles as unknown) = undefined
|
||||
;(this.offlineRoles as unknown) = undefined
|
||||
;(this.storageService as unknown) = undefined
|
||||
;(this.apiService as unknown) = undefined
|
||||
;(this.itemManager as unknown) = undefined
|
||||
@@ -793,7 +830,7 @@ export class SNFeaturesService
|
||||
override getDiagnostics(): Promise<DiagnosticInfo | undefined> {
|
||||
return Promise.resolve({
|
||||
features: {
|
||||
roles: this.roles,
|
||||
roles: this.onlineRoles,
|
||||
features: this.features,
|
||||
enabledExperimentalFeatures: this.enabledExperimentalFeatures,
|
||||
needsInitialFeaturesUpdate: this.needsInitialFeaturesUpdate,
|
||||
|
||||
@@ -66,10 +66,9 @@ export class SNSettingsService extends AbstractService implements SettingsClient
|
||||
return this.frequencyOptionsLabels[frequency]
|
||||
}
|
||||
|
||||
getCloudProviderIntegrationUrl(cloudProviderName: CloudProvider, isDevEnvironment: boolean): string {
|
||||
const { Dev, Prod } = ExtensionsServerURL
|
||||
const extServerUrl = isDevEnvironment ? Dev : Prod
|
||||
return `${extServerUrl}/${this.cloudProviderIntegrationUrlEndpoints[cloudProviderName]}?redirect_url=${extServerUrl}/components/cloudlink?`
|
||||
getCloudProviderIntegrationUrl(cloudProviderName: CloudProvider): string {
|
||||
const { Prod } = ExtensionsServerURL
|
||||
return `${Prod}/${this.cloudProviderIntegrationUrlEndpoints[cloudProviderName]}?redirect_url=${Prod}/components/cloudlink?`
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
|
||||
@@ -60,8 +60,8 @@ describe('features', () => {
|
||||
|
||||
describe('new user roles received on api response meta', () => {
|
||||
it('should save roles and features', async () => {
|
||||
expect(application.featuresService.roles).to.have.lengthOf(1)
|
||||
expect(application.featuresService.roles[0]).to.equal('CORE_USER')
|
||||
expect(application.featuresService.onlineRoles).to.have.lengthOf(1)
|
||||
expect(application.featuresService.onlineRoles[0]).to.equal('CORE_USER')
|
||||
|
||||
expect(application.featuresService.features).to.have.lengthOf(3)
|
||||
expect(application.featuresService.features[0]).to.containSubset(midnightThemeFeature)
|
||||
@@ -115,7 +115,7 @@ describe('features', () => {
|
||||
// Wipe items from initial sync
|
||||
await application.itemManager.removeAllItemsFromMemory()
|
||||
// Wipe roles from initial sync
|
||||
await application.featuresService.setRoles([])
|
||||
await application.featuresService.setOnlineRoles([])
|
||||
// Create pre-existing item for theme without all the info
|
||||
await application.itemManager.createItem(
|
||||
ContentType.Theme,
|
||||
@@ -165,7 +165,7 @@ describe('features', () => {
|
||||
.find((theme) => theme.identifier === midnightThemeFeature.identifier)
|
||||
|
||||
// Wipe roles from initial sync
|
||||
await application.featuresService.setRoles([])
|
||||
await application.featuresService.setOnlineRoles([])
|
||||
|
||||
// Call sync intentionally to get roles again in meta
|
||||
await application.sync.sync()
|
||||
@@ -184,7 +184,7 @@ describe('features', () => {
|
||||
})
|
||||
|
||||
it('should provide feature', async () => {
|
||||
const feature = application.features.getUserFeature(FeatureIdentifier.PlusEditor)
|
||||
const feature = application.features.getFeatureThatOriginallyCameFromServer(FeatureIdentifier.PlusEditor)
|
||||
expect(feature).to.containSubset(plusEditorFeature)
|
||||
})
|
||||
|
||||
|
||||
@@ -645,6 +645,8 @@ describe('keys', function () {
|
||||
|
||||
expect(Object.keys(clientBUndecryptables).length).to.equal(1)
|
||||
expect(Object.keys(clientAUndecryptables).length).to.equal(0)
|
||||
|
||||
await contextB.deinit()
|
||||
})
|
||||
|
||||
describe('changing password on 003 client while signed into 004 client should', function () {
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('online conflict handling', function () {
|
||||
|
||||
afterEach(async function () {
|
||||
if (!this.application.dealloced) {
|
||||
await Factory.safeDeinit(this.application)
|
||||
await this.context.deinit()
|
||||
}
|
||||
localStorage.clear()
|
||||
})
|
||||
@@ -950,6 +950,7 @@ describe('online conflict handling', function () {
|
||||
expect(contextA.findNoteByTitle('title-B').payload.updated_at_timestamp).to.equal(noteBExpectedTimestamp)
|
||||
|
||||
await this.sharedFinalAssertions()
|
||||
await contextB.deinit()
|
||||
}).timeout(20000)
|
||||
|
||||
it('editing original note many times after conflict on other client should only result in 2 cumulative notes', async function () {
|
||||
@@ -979,5 +980,6 @@ describe('online conflict handling', function () {
|
||||
expect(contextB.noteCount).to.equal(2)
|
||||
|
||||
await this.sharedFinalAssertions()
|
||||
await contextB.deinit()
|
||||
}).timeout(20000)
|
||||
})
|
||||
|
||||
@@ -1052,5 +1052,7 @@ describe('online syncing', function () {
|
||||
await contextB.sync()
|
||||
|
||||
expect(contextB.application.items.allCountableNotesCount()).to.equal(0)
|
||||
|
||||
await contextB.deinit()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user