一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
266 行
7.8 KiB
TypeScript
266 行
7.8 KiB
TypeScript
import { defaultState } from '../../reducer';
|
|
import SyncTargetRegistry from '../../SyncTargetRegistry';
|
|
import { FolderEntity, NoteEntity } from '../database/types';
|
|
import { getTrashFolderId } from '../trash';
|
|
import stateToWhenClauseContext from './stateToWhenClauseContext';
|
|
|
|
let mockNoteLockEnabled = true;
|
|
jest.mock('../noteLock/isNoteLockEnabled', () => () => mockNoteLockEnabled);
|
|
|
|
interface StateOptions {
|
|
folders: FolderEntity[];
|
|
notes: NoteEntity[];
|
|
selectedFolderId: string;
|
|
selectedFolderIds: string[];
|
|
selectedNoteIds: string[];
|
|
notesParentType: string;
|
|
noteLockSessionUnlocked: boolean;
|
|
activeNoteIsUndecryptable: boolean;
|
|
}
|
|
const buildState = (options: Partial<StateOptions>) => {
|
|
return {
|
|
...defaultState,
|
|
selectedFolderIds: options.selectedFolderId ? [options.selectedFolderId] : [],
|
|
...options,
|
|
};
|
|
};
|
|
|
|
describe('stateToWhenClauseContext', () => {
|
|
it.each([
|
|
{
|
|
label: 'the note lock session is locked',
|
|
noteLockSessionUnlocked: false,
|
|
activeNoteIsUndecryptable: false,
|
|
isLocked: 1,
|
|
expected: true,
|
|
},
|
|
{
|
|
label: 'the active note cannot be decrypted',
|
|
noteLockSessionUnlocked: true,
|
|
activeNoteIsUndecryptable: true,
|
|
isLocked: 1,
|
|
expected: true,
|
|
},
|
|
{
|
|
label: 'the locked note has been decrypted',
|
|
noteLockSessionUnlocked: true,
|
|
activeNoteIsUndecryptable: false,
|
|
isLocked: 1,
|
|
expected: false,
|
|
},
|
|
{
|
|
label: 'the active note is not locked',
|
|
noteLockSessionUnlocked: false,
|
|
activeNoteIsUndecryptable: true,
|
|
isLocked: 0,
|
|
expected: false,
|
|
},
|
|
{
|
|
label: 'the decrypted note is inside a share',
|
|
noteLockSessionUnlocked: true,
|
|
activeNoteIsUndecryptable: false,
|
|
isLocked: 1,
|
|
shareId: 'share-1',
|
|
expected: true,
|
|
},
|
|
])('should make the note read-only when $label', ({ noteLockSessionUnlocked, activeNoteIsUndecryptable, isLocked, shareId, expected }) => {
|
|
const applicationState = buildState({
|
|
selectedNoteIds: ['1'],
|
|
notes: [{ id: '1', is_locked: isLocked, share_id: shareId, deleted_time: 0 }],
|
|
noteLockSessionUnlocked,
|
|
activeNoteIsUndecryptable,
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState);
|
|
|
|
expect(resultingState.noteIsReadOnly).toBe(expected);
|
|
expect(resultingState.noteIsReadOnlyShare).toBe(false);
|
|
});
|
|
|
|
it('should make a locked note inside a share read-only with the note lock feature turned off', () => {
|
|
mockNoteLockEnabled = false;
|
|
try {
|
|
const applicationState = buildState({
|
|
selectedNoteIds: ['1'],
|
|
notes: [{ id: '1', is_locked: 1, share_id: 'share-1', deleted_time: 0 }],
|
|
noteLockSessionUnlocked: false,
|
|
});
|
|
|
|
expect(stateToWhenClauseContext(applicationState).noteIsReadOnly).toBe(true);
|
|
} finally {
|
|
mockNoteLockEnabled = true;
|
|
}
|
|
});
|
|
|
|
it('should be in trash if selected note has been deleted and selected folder is trash', async () => {
|
|
const applicationState = buildState({
|
|
selectedNoteIds: ['1'],
|
|
selectedFolderId: getTrashFolderId(),
|
|
notes: [
|
|
{ id: '1', deleted_time: 1722567036580 },
|
|
],
|
|
folders: [],
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState);
|
|
|
|
expect(resultingState.inTrash).toBe(true);
|
|
});
|
|
|
|
it('should NOT be in trash if selected note has not been deleted', async () => {
|
|
const applicationState = buildState({
|
|
selectedNoteIds: ['1'],
|
|
selectedFolderId: getTrashFolderId(),
|
|
notes: [
|
|
{ id: '1', deleted_time: 0 },
|
|
],
|
|
folders: [],
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState);
|
|
|
|
expect(resultingState.inTrash).toBe(false);
|
|
});
|
|
|
|
it('should NOT be in trash if selected folder is not trash', async () => {
|
|
const applicationState = buildState({
|
|
selectedNoteIds: ['1'],
|
|
selectedFolderId: 'any-other-folder',
|
|
notes: [
|
|
{ id: '1', deleted_time: 1722567036580 },
|
|
],
|
|
folders: [],
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState);
|
|
|
|
expect(resultingState.inTrash).toBe(false);
|
|
});
|
|
|
|
it('should be in trash if command folder is deleted', async () => {
|
|
const applicationState = buildState({
|
|
notes: [],
|
|
notesParentType: 'Folder',
|
|
folders: [
|
|
{ id: '1', deleted_time: 1722567036580, share_id: '', parent_id: '' },
|
|
],
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState, { commandFolderId: '1' });
|
|
|
|
expect(resultingState.inTrash).toBe(true);
|
|
});
|
|
|
|
it('should NOT be in trash if command folder is not deleted', async () => {
|
|
const applicationState = buildState({
|
|
notes: [],
|
|
folders: [
|
|
{ id: '1', deleted_time: 0, share_id: '', parent_id: '' },
|
|
],
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState, { commandFolderId: '1' });
|
|
|
|
expect(resultingState.inTrash).toBe(false);
|
|
});
|
|
|
|
it('should not be in trash if viewing all notes', async () => {
|
|
const applicationState = buildState({
|
|
selectedFolderId: 'folder',
|
|
notesParentType: 'SmartFolder',
|
|
});
|
|
const resultingState = stateToWhenClauseContext(applicationState);
|
|
|
|
expect(resultingState.inTrash).toBe(false);
|
|
});
|
|
|
|
it.each(SyncTargetRegistry.allIds().map(id => ({
|
|
id,
|
|
name: SyncTargetRegistry.idToName(id),
|
|
expected: SyncTargetRegistry.isJoplinServerOrCloud(id),
|
|
})))('should set joplinServerConnected to $expected when the sync target is $name', ({ id, expected }) => {
|
|
const getWhenClauseContext = (syncTarget: number) => {
|
|
const applicationState = {
|
|
...defaultState,
|
|
settings: {
|
|
'sync.target': syncTarget,
|
|
},
|
|
};
|
|
return stateToWhenClauseContext(applicationState);
|
|
};
|
|
|
|
const whenClauseContext = getWhenClauseContext(id);
|
|
expect(whenClauseContext.joplinServerConnected).toBe(expected);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
label: 'should be true when all target folders are deleted',
|
|
commandFolderIds: ['del1', 'del2'],
|
|
expectedDeletedState: true,
|
|
},
|
|
{
|
|
label: 'should be false when one or more target folders is not deleted',
|
|
commandFolderIds: ['del1', 'del2', '1'],
|
|
expectedDeletedState: false,
|
|
},
|
|
{
|
|
label: 'should default to the state of the selected items',
|
|
commandFolderIds: null,
|
|
expectedDeletedState: false,
|
|
},
|
|
])('should set foldersAreDeleted correctly: $label', ({ commandFolderIds, expectedDeletedState }) => {
|
|
const applicationState = buildState({
|
|
folders: [
|
|
{ id: '1', deleted_time: 0, share_id: '', parent_id: '' },
|
|
{ id: 'del1', deleted_time: 1, share_id: '', parent_id: '' },
|
|
{ id: 'del2', deleted_time: 1, share_id: '', parent_id: '' },
|
|
],
|
|
selectedFolderIds: ['1'],
|
|
selectedFolderId: '1',
|
|
notesParentType: 'Folder',
|
|
});
|
|
|
|
expect(
|
|
stateToWhenClauseContext(applicationState, { commandFolderIds }),
|
|
).toHaveProperty('foldersAreDeleted', expectedDeletedState);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
label: 'should be false when no folders exist (new profile)',
|
|
selectedFolderId: 'non-existent-folder',
|
|
folders: [],
|
|
expected: false,
|
|
},
|
|
{
|
|
label: 'should be false when selectedFolderId is null',
|
|
selectedFolderId: null,
|
|
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
|
|
expected: false,
|
|
},
|
|
{
|
|
label: 'should be false when selected folder has been deleted',
|
|
selectedFolderId: '1',
|
|
folders: [{ id: '1', deleted_time: 1000, share_id: '', parent_id: '' }],
|
|
expected: false,
|
|
},
|
|
{
|
|
label: 'should be true when selected folder exists and is not deleted',
|
|
selectedFolderId: '1',
|
|
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
|
|
expected: true,
|
|
},
|
|
{
|
|
label: 'should be false when selectedFolderId does not match any folder',
|
|
selectedFolderId: 'stale-id',
|
|
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
|
|
expected: false,
|
|
},
|
|
])('should set selectedFolderIsValid correctly: $label', ({ selectedFolderId, folders, expected }) => {
|
|
const applicationState = buildState({
|
|
selectedFolderId,
|
|
folders,
|
|
notes: [],
|
|
});
|
|
|
|
expect(
|
|
stateToWhenClauseContext(applicationState),
|
|
).toHaveProperty('selectedFolderIsValid', expected);
|
|
});
|
|
});
|