文件
mynode/packages/lib/services/ExternalEditWatcher.test.ts
chen 301194963b
一些检查失败了
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
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

141 行
4.1 KiB
TypeScript

import { setupDatabaseAndSynchronizer, switchClient } from '../testing/test-utils';
import ExternalEditWatcher from './ExternalEditWatcher';
import { appendFile, readFile, utimes } from 'fs/promises';
import Note from '../models/Note';
import { msleep, Second } from '@joplin/utils/time';
import waitFor from '../testing/waitFor';
import { NoteEntity } from './database/types';
const createAndWatchNotes = async (notes: NoteEntity[]) => {
const watcher = new ExternalEditWatcher();
const openedPaths: string[] = [];
watcher.initialize(jest.fn(() => ({
openItem: (filePath: string)=>{
openedPaths.push(filePath);
},
})), jest.fn());
const savedNotes = [];
for (const note of notes) {
const savedNote = await Note.save(note);
await watcher.openAndWatch(savedNote);
savedNotes.push(savedNote);
}
expect(openedPaths).toHaveLength(savedNotes.length);
return {
notes: savedNotes,
filePaths: openedPaths,
watcher,
};
};
const createAndWatchNote = async (note: NoteEntity) => {
const { notes, filePaths, watcher } = await createAndWatchNotes([note]);
return {
note: notes[0],
filePath: filePaths[0],
watcher,
};
};
describe('ExternalEditWatcher', () => {
beforeEach(async () => {
await setupDatabaseAndSynchronizer(0);
await switchClient(0);
jest.useRealTimers();
});
test('should handle rapid changes to a file', async () => {
const { filePath, watcher, note } = await createAndWatchNote({
title: 'Testing',
body: 'test',
});
try {
await appendFile(filePath, '1');
// Change the note several times, with a brief delay between each:
await msleep(10);
await appendFile(filePath, '2');
await msleep(10);
await appendFile(filePath, '3');
await msleep(10);
await appendFile(filePath, '4');
// Should detect both changes
await waitFor(async () => {
expect(await Note.load(note.id)).toMatchObject({
title: 'Testing',
body: 'test1234',
});
});
} finally {
await watcher.stopWatchingAll();
}
});
test('should detect changes made to different files', async () => {
const { filePaths, watcher, notes } = await createAndWatchNotes([
{ title: 'Test 1', body: 'Test' },
{ title: 'Test 2', body: 'Test' },
]);
try {
await appendFile(filePaths[0], ' (updated 1)');
await appendFile(filePaths[1], ' (updated 2)');
await waitFor(async () => {
expect(await Note.load(notes[0].id)).toMatchObject({
title: 'Test 1',
body: 'Test (updated 1)',
});
expect(await Note.load(notes[1].id)).toMatchObject({
title: 'Test 2',
body: 'Test (updated 2)',
});
});
} finally {
await watcher.stopWatchingAll();
}
});
// In some cases, multiple "update" events are received when Joplin updates an external
// editor's temporary file. If an update event doesn't change the temp file's content (e.g.
// if the update event only adjusts the timestamp), Joplin should ignore the event to avoid
// overwriting the note with old content.
// See #16350.
test('should ignore change events that don\'t change the file\'s content', async () => {
const { filePaths, watcher, notes } = await createAndWatchNotes([
{ title: 'Test', body: 'Initial content' },
]);
try {
// Apply an edit and wait to ensure that the initial watcher events
// have been processed.
await appendFile(filePaths[0], ' (updated)');
await waitFor(async () => {
expect(await Note.load(notes[0].id)).toMatchObject({
title: 'Test',
body: 'Initial content (updated)',
});
});
await Note.save({ id: notes[0].id, body: 'Out-of-sync' });
expect(await readFile(filePaths[0], 'utf-8')).toContain('Initial content');
// Updating the file without changing its content should not overwrite the new content
// in the note:
await utimes(filePaths[0], new Date(new Date().getTime() + Second), new Date(new Date().getTime() + Second));
// Should be unchanged after a brief delay
await msleep(200);
// ...and after any pending change events are applied
await watcher.stopWatching(notes[0].id);
expect(await Note.load(notes[0].id)).toMatchObject({ body: 'Out-of-sync' });
} finally {
await watcher.stopWatchingAll();
}
});
});