import type { BlockView as HostBlockView } from '../../../src/codeReader/blockView' import type { BlockView } from './types' import { describe, expect, it } from 'vitest' import { toBlockViews } from '../../../src/codeReader/blockView' import { pageBlocks } from '../../../src/codeReader/editor/pageBlocks' import { createPythonAdapter } from '../../../src/codeReader/languages/python' import { WASM_DIR, withBlocks } from '../../../src/codeReader/testSupport' import { pythonZhTranslator } from '../../../src/codeReader/translators/pythonZh' import { placementOf } from './placement' const py = createPythonAdapter(WASM_DIR) async function views(source: string): Promise { return withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, pythonZhTranslator)) } function flatten(list: T[]): T[] { return list.flatMap(block => [block, ...flatten(block.children)]) } function byId(blocks: BlockView[]): Map { return new Map(flatten(blocks).map(block => [block.id, block])) } /** 一块自己的样子(不连子块):子块变了不算它变了 */ function own({ children: _, ...rest }: BlockView): Omit { return rest } const SOURCE = [ 'import os', '', 'def helper(value):', ' return value + 1', '', 'class Store:', ' def load(self, key):', ' if key:', ' return os.getenv(key)', ' return None', '', 'LIMIT = 3', ].join('\n') describe('块在文件里的位置:扩展端记成相对的,页面累加回来', () => { it('每块累加出来的首行、尾行、开头的下标同扩展端的一样;块里的下标加上块的开头就是文件里的', async () => { const host = await views(SOURCE) const placement = placementOf(pageBlocks(host, SOURCE)) const lineStarts = SOURCE.split('\n').map((_, line, lines) => lines.slice(0, line).join('\n').length + (line > 0 ? 1 : 0)) for (const block of flatten(host)) { expect(placement.of(block.id), block.id).toEqual({ offset: lineStarts[block.startLine], line: block.startLine, endLine: block.endLine }) } const page = byId(pageBlocks(host, SOURCE)) for (const block of flatten(host).filter(block => block.defines)) { const base = placement.of(block.id)!.offset const defines = page.get(block.id)!.defines! expect(SOURCE.slice(base + defines.startIndex, base + defines.endIndex), block.id).toBe(SOURCE.slice(block.defines!.startIndex, block.defines!.endIndex)) } }) it('靠前的一块里改了一个字:只有那块变了,包着它的块只是长了一个字,后面的块发出去一模一样', async () => { const edited = SOURCE.replace('value + 1', 'value + 10') const before = byId(pageBlocks(await views(SOURCE), SOURCE)) const after = byId(pageBlocks(await views(edited), edited)) const changed = [...after.keys()].filter(id => JSON.stringify(own(after.get(id)!)) !== JSON.stringify(own(before.get(id)!))) expect(changed).toEqual(['1', '1.0']) expect(own(after.get('1')!)).toEqual({ ...own(before.get('1')!), span: { ...before.get('1')!.span, length: before.get('1')!.span.length + 1 } }) }) it('上面多了几行:只有紧跟在后面的那块离前一块远了,其余块发出去一模一样,累加出来的位置都往后挪', async () => { const shifted = SOURCE.replace('import os\n', 'import os\n\n\n') const old = pageBlocks(await views(SOURCE), SOURCE) const page = pageBlocks(await views(shifted), shifted) const before = byId(old) const after = byId(page) const changed = [...after.keys()].filter(id => JSON.stringify(own(after.get(id)!)) !== JSON.stringify(own(before.get(id)!))) expect(changed).toEqual(['1']) expect(after.get('1')!.span).toEqual({ ...before.get('1')!.span, lineGap: before.get('1')!.span.lineGap + 2, gap: before.get('1')!.span.gap + 2 }) for (const id of [...after.keys()].filter(id => id !== '0')) { const was = placementOf(old).of(id)! expect(placementOf(page).of(id), id).toEqual({ offset: was.offset + 2, line: was.line + 2, endLine: was.endLine + 2 }) } }) })