mirror of
https://github.com/moyf/easy-copy.git
synced 2026-07-22 12:20:26 +00:00
181 lines
5.3 KiB
TypeScript
181 lines
5.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import {
|
||
decidePasteResolution,
|
||
PasteResolutionInput,
|
||
shouldOmitAliasForSameFile,
|
||
shouldRegisterPasteHandler,
|
||
} from './pasteResolution';
|
||
import { CopyMetadata } from './copyMetadata';
|
||
import { LinkFormat } from './type';
|
||
|
||
describe('shouldRegisterPasteHandler', () => {
|
||
it('returns true when toggle is on', () => {
|
||
expect(shouldRegisterPasteHandler({ resolveLinkPathOnPaste: true })).toBe(true);
|
||
});
|
||
|
||
it('returns false when toggle is off', () => {
|
||
expect(shouldRegisterPasteHandler({ resolveLinkPathOnPaste: false })).toBe(false);
|
||
});
|
||
});
|
||
|
||
const META: CopyMetadata = {
|
||
clipboardText: '[[note#Heading]]',
|
||
sourceFilePath: 'notes/note.md',
|
||
subpath: '#Heading',
|
||
alias: 'Heading',
|
||
isEmbed: false,
|
||
timestamp: 1_000_000,
|
||
};
|
||
|
||
const TTL = 5 * 60 * 1000;
|
||
|
||
const baseInput: PasteResolutionInput = {
|
||
defaultPrevented: false,
|
||
resolveLinkPathOnPaste: true,
|
||
lastCopyMeta: META,
|
||
clipboardText: META.clipboardText,
|
||
now: META.timestamp + 1000,
|
||
ttlMs: TTL,
|
||
};
|
||
|
||
describe('decidePasteResolution', () => {
|
||
it('returns rewrite when all guards pass', () => {
|
||
expect(decidePasteResolution(baseInput)).toBe('rewrite');
|
||
});
|
||
|
||
it('skips when another handler already preventDefault\'d', () => {
|
||
expect(decidePasteResolution({ ...baseInput, defaultPrevented: true })).toBe('skip');
|
||
});
|
||
|
||
it('skips when the toggle is off', () => {
|
||
expect(decidePasteResolution({ ...baseInput, resolveLinkPathOnPaste: false })).toBe('skip');
|
||
});
|
||
|
||
it('skips when there is no lastCopyMeta', () => {
|
||
expect(decidePasteResolution({ ...baseInput, lastCopyMeta: null })).toBe('skip');
|
||
});
|
||
|
||
it('resets and skips when meta is older than TTL', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
now: META.timestamp + TTL + 1,
|
||
})).toBe('reset-and-skip');
|
||
});
|
||
|
||
it('does not consider meta stale at exactly the TTL boundary', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
now: META.timestamp + TTL,
|
||
})).toBe('rewrite');
|
||
});
|
||
|
||
it('resets and skips when clipboard text differs from copied text', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
clipboardText: 'something else entirely',
|
||
})).toBe('reset-and-skip');
|
||
});
|
||
|
||
it('resets and skips when clipboard text is undefined', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
clipboardText: undefined,
|
||
})).toBe('reset-and-skip');
|
||
});
|
||
|
||
it('defaultPrevented takes precedence over toggle and other state', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
defaultPrevented: true,
|
||
resolveLinkPathOnPaste: false,
|
||
lastCopyMeta: null,
|
||
})).toBe('skip');
|
||
});
|
||
|
||
it('toggle takes precedence over meta state', () => {
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
resolveLinkPathOnPaste: false,
|
||
lastCopyMeta: null,
|
||
})).toBe('skip');
|
||
});
|
||
|
||
it('TTL check runs before clipboard match', () => {
|
||
// 过期的 meta 加上不匹配的剪贴板文本——TTL 检查会先触发,
|
||
// 两种情况下结果都是 reset-and-skip,但根因是过期。
|
||
expect(decidePasteResolution({
|
||
...baseInput,
|
||
now: META.timestamp + TTL + 1,
|
||
clipboardText: 'mismatch',
|
||
})).toBe('reset-and-skip');
|
||
});
|
||
});
|
||
|
||
describe('shouldOmitAliasForSameFile', () => {
|
||
const sameFile = 'notes/SomeThing.md';
|
||
const otherFile = 'notes/MyNote.md';
|
||
|
||
const base = {
|
||
effectiveLinkFormat: LinkFormat.WIKILINK,
|
||
sourceFilePath: sameFile,
|
||
destFilePath: sameFile,
|
||
subpath: '#Other Heading',
|
||
alias: 'Other Heading',
|
||
useHeadingAsDisplayText: true,
|
||
};
|
||
|
||
it('omits when WIKI + same-file + heading subpath matches alias (sanitized)', () => {
|
||
expect(shouldOmitAliasForSameFile(base)).toBe(true);
|
||
});
|
||
|
||
it('keeps alias when WIKI + same-file but alias does not match heading', () => {
|
||
expect(shouldOmitAliasForSameFile({ ...base, alias: 'Different' })).toBe(false);
|
||
});
|
||
|
||
it('keeps alias when useHeadingAsDisplayText is false', () => {
|
||
expect(shouldOmitAliasForSameFile({
|
||
...base,
|
||
useHeadingAsDisplayText: false,
|
||
alias: 'SomeThing#Other Heading',
|
||
})).toBe(false);
|
||
});
|
||
|
||
it('keeps alias on cross-file paste (WIKI)', () => {
|
||
expect(shouldOmitAliasForSameFile({ ...base, destFilePath: otherFile })).toBe(false);
|
||
});
|
||
|
||
it('keeps alias for MD format even on same-file', () => {
|
||
expect(shouldOmitAliasForSameFile({ ...base, effectiveLinkFormat: LinkFormat.MDLINK })).toBe(false);
|
||
});
|
||
|
||
it('keeps alias when subpath is empty', () => {
|
||
expect(shouldOmitAliasForSameFile({ ...base, subpath: '' })).toBe(false);
|
||
});
|
||
|
||
it('keeps alias when alias is empty', () => {
|
||
expect(shouldOmitAliasForSameFile({ ...base, alias: '' })).toBe(false);
|
||
});
|
||
|
||
it('keeps alias on block-link subpath (#^id)', () => {
|
||
expect(shouldOmitAliasForSameFile({
|
||
...base,
|
||
subpath: '#^abc123',
|
||
alias: 'The quick brown',
|
||
})).toBe(false);
|
||
});
|
||
|
||
it('omits when sanitization round-trip collapses alias to subpath', () => {
|
||
// alias 中的 | 会被 sanitizeHeadingForLink 折叠为空格
|
||
expect(shouldOmitAliasForSameFile({
|
||
...base,
|
||
subpath: '#Some Heading',
|
||
alias: 'Some|Heading',
|
||
})).toBe(true);
|
||
});
|
||
|
||
it('keeps alias when OBSIDIAN is passed (helper expects already-resolved format)', () => {
|
||
// 防御性检查:调用方不应在此传入 OBSIDIAN;
|
||
// 即使误传,本函数也会安全地返回 false。
|
||
expect(shouldOmitAliasForSameFile({ ...base, effectiveLinkFormat: LinkFormat.OBSIDIAN })).toBe(false);
|
||
});
|
||
});
|