feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,140 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户