178 lines
8.6 KiB
TypeScript
178 lines
8.6 KiB
TypeScript
import type { BlockView, DocumentDiff, RemovedView } from './types'
|
||
import { describe, expect, it } from 'vitest'
|
||
import { changeStops, diffTree, foldUnchanged, nextStop, openedByDiff, removedTitle, statsParts } from './diffView'
|
||
|
||
function block(id: string, anchor: string, children: BlockView[] = [], extra: Partial<BlockView> = {}): BlockView {
|
||
return { id, anchor, kind: 'function', startLine: 0, endLine: 0, code: '', codeTokens: [], attributes: { kind: 'function' }, concepts: [{ id: 'function', label: '函数', icon: 'square-function' }], children, translation: [], ...extra } as BlockView
|
||
}
|
||
|
||
function removed(parent: string, after: string | undefined, kind = 'function'): RemovedView {
|
||
return { parent, ...(after !== undefined && { after }), block: block('9', `${kind} gone`, [], { kind }), line: 3, kindName: '函数' }
|
||
}
|
||
|
||
function diff(parts: Partial<DocumentDiff>): DocumentDiff {
|
||
return { base: '上一次提交', version: 2, blocks: {}, removed: [], stats: { add: 0, mod: 0, del: 0 }, lineCount: 10, ...parts }
|
||
}
|
||
|
||
const BLOCKS = [
|
||
block('0', 'function a', [block('0.0', 'function a > return @1')]),
|
||
block('1', 'function b'),
|
||
]
|
||
|
||
describe('diffTree', () => {
|
||
it('块按记号认成页面上的 id;页面上找不到的记号(文档刚改)不标', () => {
|
||
const tree = diffTree(BLOCKS, diff({ blocks: { 'function a': { change: 'mod' }, 'function a > return @1': { change: 'mod', own: true }, 'function gone': { change: 'add' } } }), 2, () => true)
|
||
expect([...tree!.changes]).toEqual([['0', { change: 'mod' }], ['0.0', { change: 'mod', own: true }]])
|
||
expect(tree!.current).toBe(true)
|
||
})
|
||
|
||
it('页面上不是差异算的那一版时,卡片里的标记不画', () => {
|
||
expect(diffTree(BLOCKS, diff({}), 3, () => true)?.current).toBe(false)
|
||
})
|
||
|
||
it('删掉的块按层挂:父块、排在后面的那块换成页面上的 id,找不到的不画,被筛掉的不画', () => {
|
||
const tree = diffTree(BLOCKS, diff({ removed: [
|
||
removed('', 'function b'),
|
||
removed('function a', undefined),
|
||
removed('function missing', undefined),
|
||
removed('', 'function missing'),
|
||
removed('', undefined, 'import'),
|
||
] }), 2, item => item.block.kind !== 'import')
|
||
expect(tree!.removed.get('')).toEqual([{ id: 'removed-0', after: '1', removed: removed('', 'function b') }])
|
||
expect(tree!.removed.get('0')).toEqual([{ id: 'removed-1', removed: removed('function a', undefined) }])
|
||
expect(tree!.removed.size).toBe(2)
|
||
})
|
||
|
||
it('不比、基准里没有这个文件时不逐块标', () => {
|
||
expect(diffTree(BLOCKS, null, 2, () => true)).toBeUndefined()
|
||
expect(diffTree(BLOCKS, diff({ newFile: '上一次提交里没有这个文件' }), 2, () => true)).toBeUndefined()
|
||
})
|
||
|
||
it('标了改动的块默认展开', () => {
|
||
const tree = diffTree(BLOCKS, diff({ blocks: { 'function b': { change: 'add', outermost: true } } }), 2, () => true)
|
||
expect([...openedByDiff(tree)]).toEqual(['1'])
|
||
expect(openedByDiff(undefined).size).toBe(0)
|
||
})
|
||
})
|
||
|
||
describe('statsParts', () => {
|
||
it('没有的那种不写', () => {
|
||
expect(statsParts({ add: 2, mod: 0, del: 1 })).toEqual([{ change: 'add', text: '新增 2' }, { change: 'del', text: '删除 1' }])
|
||
expect(statsParts({ add: 0, mod: 0, del: 0 })).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('removedTitle', () => {
|
||
it('属性表取第一行的值,一行译文取译文,只取第一行', () => {
|
||
expect(removedTitle(block('0', 'x', [], { properties: [{ label: '名称', value: [{ text: '_is_owner', role: 'function' }] }] } as Partial<BlockView>))).toBe('_is_owner')
|
||
expect(removedTitle(block('0', 'x', [], { translation: [{ text: '调用 f\n接着', role: 'plain' }] }))).toBe('调用 f')
|
||
expect(removedTitle(block('0', 'x', [], { kind: 'comment', translation: { markdown: '说明第一行\n第二行' } }))).toBe('说明第一行')
|
||
})
|
||
})
|
||
|
||
/**
|
||
* 0 function a(修改)
|
||
* 0.0 import(没变)
|
||
* 0.1 return(修改,自己改了)
|
||
* 0.2 return(没变)
|
||
* 1 function b(没变)
|
||
* 2 function c(没变)
|
||
* 3 function d(新增,带子块 3.0)
|
||
* 4 function e(没变)
|
||
*/
|
||
const TREE_BLOCKS = [
|
||
block('0', 'a', [block('0.0', 'a0', [], { startLine: 1 }), block('0.1', 'a1', [], { startLine: 2, endLine: 2 }), block('0.2', 'a2', [], { startLine: 3 })], { startLine: 0, endLine: 3 }),
|
||
block('1', 'b', [], { startLine: 5 }),
|
||
block('2', 'c', [], { startLine: 7 }),
|
||
block('3', 'd', [block('3.0', 'd0')], { startLine: 9, endLine: 11 }),
|
||
block('4', 'e', [], { startLine: 13 }),
|
||
]
|
||
|
||
function tree(removedList: RemovedView[] = []): NonNullable<ReturnType<typeof diffTree>> {
|
||
return diffTree(TREE_BLOCKS, diff({
|
||
blocks: { a: { change: 'mod' }, a1: { change: 'mod', own: true }, d: { change: 'add', outermost: true }, d0: { change: 'add' } },
|
||
removed: removedList,
|
||
}), 2, () => true)!
|
||
}
|
||
|
||
describe('foldUnchanged', () => {
|
||
it('每层连着没变的块折成一段,改过的块下面同样按层折', () => {
|
||
const { runs, folded } = foldUnchanged(TREE_BLOCKS, tree(), () => true, new Set(), new Set())
|
||
expect([...runs]).toEqual([['0.0', 1], ['0.2', 1], ['1', 2], ['4', 1]])
|
||
expect([...folded].sort()).toEqual(['0.0', '0.2', '1', '2', '4'])
|
||
})
|
||
|
||
it('删掉的块排在哪块后面,那一段就在哪儿断开', () => {
|
||
const { runs } = foldUnchanged(TREE_BLOCKS, tree([removed('', 'b')]), () => true, new Set(), new Set())
|
||
expect([...runs]).toContainEqual(['1', 1])
|
||
expect([...runs]).toContainEqual(['2', 1])
|
||
})
|
||
|
||
it('被筛掉的块不算也不打断;点开过的段、要露出来的块不折', () => {
|
||
expect([...foldUnchanged(TREE_BLOCKS, tree(), block => block.id !== '2', new Set(), new Set()).runs]).toContainEqual(['1', 1])
|
||
expect(foldUnchanged(TREE_BLOCKS, tree(), () => true, new Set(['1']), new Set()).runs.has('1')).toBe(false)
|
||
expect(foldUnchanged(TREE_BLOCKS, tree(), () => true, new Set(), new Set(['2'])).folded.has('2')).toBe(false)
|
||
})
|
||
})
|
||
|
||
/** 一张卡片:两个子块挂在第 8 行的子行上,后面一个留下的子块;只有第二个子块改了,它里面挂在第 2 行子行上的子块自己改了 */
|
||
const CARD_BLOCKS = [
|
||
block('0', 'card', [
|
||
block('0.0', 'm1', [], { slot: { row: 8, item: 0 } }),
|
||
block('0.1', 'm2', [block('0.1.0', 'm2s', [], { slot: { row: 2, item: 0 } })], { slot: { row: 8, item: 1 }, startLine: 4 }),
|
||
block('0.2', 'rest'),
|
||
]),
|
||
]
|
||
|
||
function cardTree(): NonNullable<ReturnType<typeof diffTree>> {
|
||
return diffTree(CARD_BLOCKS, diff({ blocks: { card: { change: 'mod' }, m2: { change: 'mod' }, m2s: { change: 'mod', own: true } } }), 2, () => true)!
|
||
}
|
||
|
||
describe('挂在卡片子行上的子块', () => {
|
||
it('只看改动:挂在子行上的不折、改了的照样往里看,卡片后面留下的照常折', () => {
|
||
const { runs, folded } = foldUnchanged(CARD_BLOCKS, cardTree(), () => true, new Set(), new Set())
|
||
expect([...runs]).toEqual([['0.2', 1]])
|
||
expect([...folded].sort()).toEqual(['0.2'])
|
||
})
|
||
|
||
it('上一处 / 下一处:要展开的是子块所在的那一行和它自己,不是卡片', () => {
|
||
expect(changeStops(CARD_BLOCKS, cardTree(), new Set()).map(stop => [stop.id, stop.ancestors])).toEqual([
|
||
['0.1.0', ['0/p8', '0.1', '0.1/p2', '0.1.0']],
|
||
])
|
||
})
|
||
})
|
||
|
||
describe('changeStops', () => {
|
||
it('新增的最外面那块、自己改了的修改块、删掉的块,照页面上的先后;修改块只算自己那几行', () => {
|
||
const stops = changeStops(TREE_BLOCKS, tree([removed('', 'c'), removed('a', undefined)]), new Set())
|
||
expect(stops.map(stop => [stop.id, stop.change, stop.from, stop.to, stop.ancestors])).toEqual([
|
||
['removed-1', 'del', 3, 3, ['0']],
|
||
['0.1', 'mod', 2, 2, ['0']],
|
||
['removed-0', 'del', 3, 3, []],
|
||
['3', 'add', 9, 11, []],
|
||
])
|
||
})
|
||
|
||
it('被筛掉的块和它下面的不算', () => {
|
||
expect(changeStops(TREE_BLOCKS, tree(), new Set(['3', '3.0'])).map(stop => stop.id)).toEqual(['0.1'])
|
||
})
|
||
})
|
||
|
||
describe('nextStop', () => {
|
||
const stops = [{ from: 2 }, { from: 9 }, { from: 20 }].map((stop, index) => ({ id: String(index), change: 'mod' as const, to: stop.from, ancestors: [], ...stop }))
|
||
|
||
it('接着刚跳到的那处走,到头绕回另一头', () => {
|
||
expect(nextStop(stops, { index: 0 }, 1)).toBe(1)
|
||
expect(nextStop(stops, { index: 2 }, 1)).toBe(0)
|
||
expect(nextStop(stops, { index: 0 }, -1)).toBe(2)
|
||
})
|
||
|
||
it('按视口顶上那块的行找:下一处在它后面,上一处在它前面', () => {
|
||
expect(nextStop(stops, { line: 9 }, 1)).toBe(2)
|
||
expect(nextStop(stops, { line: 9 }, -1)).toBe(0)
|
||
expect(nextStop(stops, { line: 30 }, 1)).toBe(0)
|
||
expect(nextStop(stops, { line: 0 }, -1)).toBe(2)
|
||
expect(nextStop(stops, {}, 1)).toBe(0)
|
||
})
|
||
})
|