Skip to content
This repository was archived by the owner on Jul 6, 2022. It is now read-only.
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
1 change: 1 addition & 0 deletions packages/snjs/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
collectCoverageFrom: [
//'lib/**/{!(index),}.ts',
'lib/services/component_manager.ts',
'lib/application.ts',
],

// The directory where Jest should output its coverage files
Expand Down
31 changes: 25 additions & 6 deletions packages/snjs/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1310,11 +1310,30 @@ public getSessions(): Promise<(HttpResponse & { data: RemoteSession[] }) | HttpR
);
}

public async signOut(): Promise<void> {
await this.credentialService.signOut();
await this.notifyEvent(ApplicationEvent.SignedOut);
await this.prepareForDeinit();
this.deinit(DeinitSource.SignOut);
public async signOut(force = false): Promise<void> {
const performSignOut = async () => {
await this.credentialService.signOut();
await this.notifyEvent(ApplicationEvent.SignedOut);
await this.prepareForDeinit();
this.deinit(DeinitSource.SignOut);
};

if (force) {
await performSignOut();
return;
}

const dirtyItems = this.itemManager.getDirtyItems();
if (dirtyItems.length > 0) {
const didConfirm = await this.alertService.confirm(
`There are ${dirtyItems.length} items with unsynced changes. If you sign out, these changes will be lost forever. Are you sure you want to sign out?`
);
if (didConfirm) {
await performSignOut();
}
} else {
await performSignOut();
}
}

public async validateAccountPassword(password: string): Promise<boolean> {
Expand Down Expand Up @@ -1636,7 +1655,7 @@ public getSessions(): Promise<(HttpResponse & { data: RemoteSession[] }) | HttpR
case SessionEvent.Revoked: {
/** Keep a reference to the soon-to-be-cleared alertService */
const alertService = this.alertService;
await this.signOut();
await this.signOut(true);
void alertService.alert(SessionStrings.CurrentSessionRevoked);
break;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/snjs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/snjs",
"version": "2.7.11",
"version": "2.7.12",
"engines": {
"node": ">=14.0.0 <16.0.0"
},
Expand Down
86 changes: 86 additions & 0 deletions packages/snjs/test/lib/application.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
Platform,
Environment,
DeinitSource,
} from '@Lib/index';
import { createApplication } from '../setup/snjs/appFactory';
import {
createNoteItem,
} from '../helpers';

describe('Application', () => {
/** The global Standard Notes application. */
let testSNApp;

beforeEach(async () => {
testSNApp = await createApplication('test-application', Environment.Web, Platform.LinuxWeb);
});

describe('signOut()', () => {
let testNote1;
let confirmAlert;
let deinit;

const signOutConfirmMessage = (numberOfItems) => {
return `There are ${numberOfItems} items with unsynced changes. ` +
'If you sign out, these changes will be lost forever. Are you sure you want to sign out?'
};

beforeEach(async () => {
testNote1 = await createNoteItem(testSNApp, {
title: 'Note 1',
text: 'This is a test note!'
});
confirmAlert = jest.spyOn(
testSNApp.alertService,
'confirm'
);
deinit = jest.spyOn(
testSNApp,
'deinit'
);
});

it('shows confirmation dialog when there are unsaved changes', async () => {
await testSNApp.itemManager.setItemDirty(testNote1.uuid);
await testSNApp.signOut();

const expectedConfirmMessage = signOutConfirmMessage(1);

expect(confirmAlert).toBeCalledTimes(1);
expect(confirmAlert).toBeCalledWith(expectedConfirmMessage);
expect(deinit).toBeCalledTimes(1);
expect(deinit).toBeCalledWith(DeinitSource.SignOut);
});

it('does not show confirmation dialog when there are no unsaved changes', async () => {
await testSNApp.signOut();

expect(confirmAlert).toBeCalledTimes(0);
expect(deinit).toBeCalledTimes(1);
expect(deinit).toBeCalledWith(DeinitSource.SignOut);
});

it('does not show confirmation dialog when there are unsaved changes and the "force" option is set to true', async () => {
await testSNApp.itemManager.setItemDirty(testNote1.uuid);
await testSNApp.signOut(true);

expect(confirmAlert).toBeCalledTimes(0);
expect(deinit).toBeCalledTimes(1);
expect(deinit).toBeCalledWith(DeinitSource.SignOut);
});

it('cancels sign out if confirmation dialog is rejected', async () => {
confirmAlert.mockImplementation((message) => false);

await testSNApp.itemManager.setItemDirty(testNote1.uuid);
await testSNApp.signOut();

const expectedConfirmMessage = signOutConfirmMessage(1);

expect(confirmAlert).toBeCalledTimes(1);
expect(confirmAlert).toBeCalledWith(expectedConfirmMessage);
expect(deinit).toBeCalledTimes(0);
});
});
});
4 changes: 3 additions & 1 deletion packages/snjs/test/setup/snjs/deviceInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ export default class DeviceInterface extends SNDeviceInterface {

async getRawKeychainValue() {
const keychain = this.localStorage.getItem(KEYCHAIN_STORAGE_KEY);
return JSON.parse(keychain);
if (keychain) {
return JSON.parse(keychain);
}
}

async clearRawKeychainValue() {
Expand Down