import type { BlockKind, BlockView } from './types' import type { ViewStateStorage } from './viewState' import { describe, expect, it } from 'vitest' import { collapsedIds, createViewStateStore, propertyItemId, propertyRowId } from './viewState' /** 模拟 VS Code 的 webview 状态:存进去的要能序列化,页面重新加载后原样取回;也能塞进旧版本存的东西 */ function memoryStorage(initial?: unknown): ViewStateStorage { let saved = initial === undefined ? undefined : JSON.stringify(initial) return { getState: () => saved === undefined ? undefined : JSON.parse(saved) as unknown, setState: (state) => { saved = JSON.stringify(state) }, } } function block(id: string, kind: BlockKind, children: BlockView[] = []): BlockView { return { id, kind, startLine: 0, endLine: 0, code: '', codeTokens: [], attributes: { kind }, children, translation: [] } } /** * 0 import * 1 function * 1.0 if * 1.0.0 function(嵌套函数) * 2 class * 2.0 function(方法) */ const BLOCKS = [ block('0', 'import'), block('1', 'function', [block('1.0', 'if', [block('1.0.0', 'function', [block('1.0.0.0', 'return')])])]), block('2', 'class', [block('2.0', 'function', [block('2.0.0', 'return')])]), ] describe('webview 状态', () => { it('第一次打开:没有编辑中的块、没点过折叠,也没有滚动位置', () => { expect(createViewStateStore(memoryStorage()).get()).toEqual({ editing: [], folds: {} }) }) it('块状态和滚动位置各自更新,互不覆盖', () => { const storage = memoryStorage() const store = createViewStateStore(storage) store.update({ editing: ['2'], folds: { 6.1: true } }) store.update({ scroll: { line: 12, offset: 30, scrollTop: 480 } }) store.update({ editing: [], folds: { 6.1: true } }) expect(storage.getState()).toEqual({ editing: [], folds: { 6.1: true }, scroll: { line: 12, offset: 30, scrollTop: 480 } }) }) it('页面重新加载后取回上次存的全部状态', () => { const storage = memoryStorage() const store = createViewStateStore(storage) store.update({ folds: { 3: true, 4: false } }) store.update({ scroll: { line: 40, offset: -6, scrollTop: 1210 } }) expect(createViewStateStore(storage).get()).toEqual({ editing: [], folds: { 3: true, 4: false }, scroll: { line: 40, offset: -6, scrollTop: 1210 } }) }) it('旧版本存的、形状不对的项当没存过,认得的项照旧取回', () => { expect(createViewStateStore(memoryStorage({ editing: ['1'], collapsed: ['2'] })).get()).toEqual({ editing: ['1'], folds: {} }) expect(createViewStateStore(memoryStorage({ editing: 'x', folds: ['2'], scroll: { line: 'a' } })).get()).toEqual({ editing: [], folds: {} }) expect(createViewStateStore(memoryStorage({ editing: [1, '2'], folds: { 1: true, 2: 'yes' } })).get()).toEqual({ editing: ['2'], folds: { 1: true } }) expect(createViewStateStore(memoryStorage(42)).get()).toEqual({ editing: [], folds: {} }) expect(createViewStateStore(memoryStorage(null)).get()).toEqual({ editing: [], folds: {} }) }) it('属性行的折叠 id 是块 id 加行序号,不会和块 id 撞上', () => { expect(propertyRowId('6.1.0', 3)).toBe('6.1.0/p3') expect(propertyRowId('6', 1)).not.toBe('6.1') expect(propertyRowId('6.1', 0)).not.toBe(propertyRowId('6', 10)) }) it('属性行和块的折叠选择存在同一份 folds 里,页面重新加载后都还在', () => { const storage = memoryStorage() createViewStateStore(storage).update({ folds: { 6.1: false, [propertyRowId('6.1.0', 3)]: true } }) expect(createViewStateStore(storage).get().folds).toEqual({ '6.1': false, '6.1.0/p3': true }) }) it('子行 id 是上一层的 id 加子行序号,一层层往后接;不同位置的不会撞上,也不会和块 id、属性行 id 撞上', () => { const row = propertyRowId('6.1.0', 3) expect(propertyItemId(row, 1)).toBe('6.1.0/p3/1') expect(propertyItemId(propertyItemId(row, 1), 0)).toBe('6.1.0/p3/1/0') const ids = [ '6', '6.1', propertyRowId('6', 1), propertyRowId('6', 11), propertyItemId(propertyRowId('6', 1), 1), propertyItemId(propertyRowId('6', 11), 1), propertyItemId(propertyItemId(propertyRowId('6', 1), 1), 1), propertyItemId(propertyItemId(propertyRowId('6', 1), 11), 1), propertyItemId(propertyItemId(propertyRowId('6', 1), 1), 11), propertyItemId(propertyRowId('6.1', 1), 1), ] expect(new Set(ids).size).toBe(ids.length) }) it('嵌套子行的折叠选择也存在 folds 里,页面重新加载后还在', () => { const storage = memoryStorage() const nested = propertyItemId(propertyItemId(propertyRowId('2', 2), 0), 1) createViewStateStore(storage).update({ folds: { [nested]: true, [propertyItemId(propertyRowId('2', 2), 0)]: false } }) expect(createViewStateStore(storage).get().folds).toEqual({ '2/p2/0/1': true, '2/p2/0': false }) }) }) describe('眼下折叠着哪些', () => { it('没点过:函数块(含方法、嵌套函数)、类块折叠,其余块和属性行展开', () => { expect([...collapsedIds(BLOCKS, {})].sort()).toEqual(['1', '1.0.0', '2', '2.0']) }) it('没点过:库规则认领的块(带 badge)和类块一样折叠,不看种类 id', () => { const table: BlockView = { ...block('0', 'if', [block('0.0', 'return')]), kind: 'sqlalchemy.table', badge: { label: '数据表', color: { dark: '#e8a860', light: '#a3590d' }, icon: 'database' } } expect([...collapsedIds([table, block('1', 'if')], {})]).toEqual(['0']) }) it('没点过:枚举块(Python 枚举下面留着方法)和类块一样折叠', () => { expect([...collapsedIds([block('0', 'enum', [block('0.0', 'function')]), block('1', 'if')], {})]).toEqual(['0', '0.0']) }) it('没点过:异常块(下面留着构造函数、方法)和类块一样折叠', () => { expect([...collapsedIds([block('0', 'exception', [block('0.0', 'function')]), block('1', 'if')], {})]).toEqual(['0', '0.0']) }) it('标了改动的块默认展开,用户折叠过的照样折叠', () => { expect([...collapsedIds(BLOCKS, {}, new Set(['1', '2']))].sort()).toEqual(['1.0.0', '2.0']) expect([...collapsedIds(BLOCKS, { 1: true }, new Set(['1']))].sort()).toEqual(['1', '1.0.0', '2', '2.0']) }) it('点过的按用户的:展开的函数、类保持展开,折叠的其他块、属性行保持折叠', () => { const folds = { '1': false, '2': false, '1.0': true, '1.0.0': false, [propertyRowId('0', 2)]: true, [propertyRowId('2.0', 1)]: false } expect([...collapsedIds(BLOCKS, folds)].sort()).toEqual(['0/p2', '1.0', '2.0']) }) it('带下一层的子行默认展开,用户折叠过的按他的', () => { const outer = propertyItemId(propertyRowId('0', 2), 0) const inner = propertyItemId(outer, 3) expect(collapsedIds(BLOCKS, {}).has(outer)).toBe(false) expect([...collapsedIds(BLOCKS, { [outer]: false, [inner]: true })].sort()).toEqual(['0/p2/0/3', '1', '1.0.0', '2', '2.0']) }) })