mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-06 07:30:16 +00:00
feat(editor): 历史记录列表展示时间并优化回滚差异弹窗
为历史步骤自动写入 timestamp 并按当天/跨天格式化展示;回滚确认弹窗区分标题与说明,关闭时清理确认回调。
This commit is contained in:
parent
42162f2e4a
commit
a9e9e65f9c
@ -15,6 +15,8 @@
|
||||
:merged="group.steps.length > 1"
|
||||
:op-type="group.opType"
|
||||
:desc="describeGroup(group)"
|
||||
:time="formatHistoryTime(groupTimestamp(group))"
|
||||
:time-title="formatHistoryFullTime(groupTimestamp(group))"
|
||||
:step-count="group.steps.length"
|
||||
:sub-steps="
|
||||
group.steps.map((s: any) => ({
|
||||
@ -24,6 +26,8 @@
|
||||
desc: describeStep(s.step),
|
||||
diffable: isStepDiffable ? isStepDiffable(s.step) : false,
|
||||
revertable: s.applied,
|
||||
time: formatHistoryTime(s.step.timestamp),
|
||||
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
||||
}))
|
||||
"
|
||||
:is-current="group.isCurrent"
|
||||
@ -54,6 +58,7 @@ import { computed } from 'vue';
|
||||
|
||||
import type { HistoryOpType } from '@editor/type';
|
||||
|
||||
import { formatHistoryFullTime, formatHistoryTime, groupTimestamp } from './composables';
|
||||
import GroupRow from './GroupRow.vue';
|
||||
import InitialRow from './InitialRow.vue';
|
||||
|
||||
|
||||
@ -13,6 +13,8 @@
|
||||
<span class="m-editor-history-list-item-op" :class="`op-${opType}`">{{ opLabel(opType) }}</span>
|
||||
<span class="m-editor-history-list-item-desc">{{ desc }}</span>
|
||||
|
||||
<span v-if="time" class="m-editor-history-list-item-time" :title="timeTitle || time">{{ time }}</span>
|
||||
|
||||
<span v-if="merged" class="m-editor-history-list-item-merge">合并 {{ stepCount }} 步</span>
|
||||
|
||||
<span
|
||||
@ -48,6 +50,7 @@
|
||||
>
|
||||
<span class="m-editor-history-list-item-index">#{{ s.index + 1 }}</span>
|
||||
<span class="m-editor-history-list-substep-desc">{{ s.desc }}</span>
|
||||
<span v-if="s.time" class="m-editor-history-list-item-time" :title="s.timeTitle || s.time">{{ s.time }}</span>
|
||||
<span
|
||||
v-if="s.revertable"
|
||||
class="m-editor-history-list-item-revert"
|
||||
@ -97,6 +100,10 @@ const props = withDefaults(
|
||||
opType: HistoryOpType;
|
||||
/** 组的整体描述文案,由上层根据 step / group 计算后传入,例如 "修改 button · style.color"。 */
|
||||
desc: string;
|
||||
/** 组头部展示的时间文案(一般为组内最近一步的时间),为空时不渲染。 */
|
||||
time?: string;
|
||||
/** 组头部时间的 title 悬浮提示(完整时间),缺省时回退为 time。 */
|
||||
timeTitle?: string;
|
||||
/** 组内的 step 总数,仅在 merged 为 true 时显示为 "合并 N 步"。 */
|
||||
stepCount: number;
|
||||
/** 子步列表,用于在展开状态下逐条展示每个 step 的索引、应用状态与描述文案。 */
|
||||
@ -108,6 +115,10 @@ const props = withDefaults(
|
||||
diffable?: boolean;
|
||||
/** 是否可对该子步执行「回滚」(已应用 + 业务侧确认支持反向)。父级根据 step 与 applied 决定。 */
|
||||
revertable?: boolean;
|
||||
/** 该子步的时间文案,为空时不渲染。 */
|
||||
time?: string;
|
||||
/** 该子步时间的 title 悬浮提示(完整时间),缺省时回退为 time。 */
|
||||
timeTitle?: string;
|
||||
}[];
|
||||
/** 当前组是否处于展开状态。仅在 merged 为 true 时生效,控制子步列表是否渲染。 */
|
||||
expanded: boolean;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<TMagicDialog
|
||||
v-model="visible"
|
||||
class="m-editor-history-diff-dialog"
|
||||
title="查看修改差异"
|
||||
:title="dialogTitle"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
@ -11,6 +11,8 @@
|
||||
@close="onClose"
|
||||
>
|
||||
<div v-if="payload" class="m-editor-history-diff-dialog-body">
|
||||
<div v-if="onConfirm" class="m-editor-history-diff-dialog-notice">仅回滚有差异的字段</div>
|
||||
|
||||
<div class="m-editor-history-diff-dialog-header">
|
||||
<span class="m-editor-history-diff-dialog-target">{{ targetText }}</span>
|
||||
<div class="m-editor-history-diff-dialog-controls">
|
||||
@ -44,6 +46,7 @@
|
||||
:last-value="leftValue"
|
||||
:extend-state="extendState"
|
||||
:load-config="loadConfig"
|
||||
:self-diff-field-types="selfDiffFieldTypes"
|
||||
height="70vh"
|
||||
/>
|
||||
|
||||
@ -100,6 +103,7 @@ const props = withDefaults(
|
||||
loadConfig?: CompareFormLoadConfig;
|
||||
width?: string;
|
||||
onConfirm?: () => void;
|
||||
selfDiffFieldTypes?: string[];
|
||||
}>(),
|
||||
{
|
||||
width: '900px',
|
||||
@ -147,6 +151,8 @@ const codeDiffOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
const dialogTitle = computed(() => (props.onConfirm ? '确认回滚' : '查看修改差异'));
|
||||
|
||||
const hasCurrent = computed(() => payload.value?.currentValue !== undefined && payload.value?.currentValue !== null);
|
||||
|
||||
/** 左侧(旧/参照)值 */
|
||||
@ -174,8 +180,10 @@ const isSameAsCurrent = computed(() => {
|
||||
|
||||
const onConfirmClick = () => {
|
||||
const cb = props.onConfirm;
|
||||
visible.value = false;
|
||||
|
||||
cb?.();
|
||||
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const targetText = computed(() => {
|
||||
|
||||
@ -95,7 +95,12 @@
|
||||
</template>
|
||||
</TMagicPopover>
|
||||
|
||||
<HistoryDiffDialog ref="diffDialog" :extend-state="extendFormState" :on-confirm="onConfirmRevert" />
|
||||
<HistoryDiffDialog
|
||||
ref="diffDialog"
|
||||
:extend-state="extendFormState"
|
||||
:on-confirm="onConfirmRevert"
|
||||
@close="onDiffDialogClose"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -368,4 +373,8 @@ const onCodeBlockRevert = (id: string | number, index: number) => {
|
||||
onConfirmRevert.value();
|
||||
}
|
||||
};
|
||||
|
||||
const onDiffDialogClose = () => {
|
||||
onConfirmRevert.value = undefined;
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
:merged="group.steps.length > 1"
|
||||
:op-type="group.opType"
|
||||
:desc="describePageGroup(group)"
|
||||
:time="formatHistoryTime(groupTimestamp(group))"
|
||||
:time-title="formatHistoryFullTime(groupTimestamp(group))"
|
||||
:step-count="group.steps.length"
|
||||
:sub-steps="
|
||||
group.steps.map((s) => ({
|
||||
@ -19,6 +21,8 @@
|
||||
desc: describePageStep(s.step),
|
||||
diffable: isPageStepDiffable(s.step),
|
||||
revertable: s.applied,
|
||||
time: formatHistoryTime(s.step.timestamp),
|
||||
timeTitle: formatHistoryFullTime(s.step.timestamp),
|
||||
}))
|
||||
"
|
||||
:is-current="group.isCurrent"
|
||||
@ -44,7 +48,13 @@ import { TMagicScrollbar } from '@tmagic/design';
|
||||
|
||||
import type { PageHistoryGroup, StepValue } from '@editor/type';
|
||||
|
||||
import { describePageGroup, describePageStep } from './composables';
|
||||
import {
|
||||
describePageGroup,
|
||||
describePageStep,
|
||||
formatHistoryFullTime,
|
||||
formatHistoryTime,
|
||||
groupTimestamp,
|
||||
} from './composables';
|
||||
import GroupRow from './GroupRow.vue';
|
||||
import InitialRow from './InitialRow.vue';
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
import { datetimeFormatter } from '@tmagic/form';
|
||||
|
||||
import { useServices } from '@editor/hooks/use-services';
|
||||
import type {
|
||||
CodeBlockHistoryGroup,
|
||||
@ -67,6 +69,32 @@ export const useHistoryList = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 历史面板的时间展示:
|
||||
* - 当天的记录只显示 `HH:mm:ss`;
|
||||
* - 跨天的记录显示 `MM-DD HH:mm:ss`。
|
||||
* 无时间戳(旧数据 / 未写入)时返回空串,UI 据此不渲染时间。
|
||||
*/
|
||||
export const formatHistoryTime = (timestamp?: number): string => {
|
||||
if (!timestamp) return '';
|
||||
const isToday =
|
||||
datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD') ===
|
||||
(datetimeFormatter(new Date(), '', 'YYYY-MM-DD') as string);
|
||||
return `${
|
||||
isToday
|
||||
? datetimeFormatter(new Date(timestamp), '', 'HH:mm:ss')
|
||||
: datetimeFormatter(new Date(timestamp), '', 'MM-DD HH:mm:ss')
|
||||
}`;
|
||||
};
|
||||
|
||||
/** 完整时间(含年份与秒),用于 title 悬浮提示。无时间戳时返回空串。 */
|
||||
export const formatHistoryFullTime = (timestamp?: number): string =>
|
||||
timestamp ? `${datetimeFormatter(new Date(timestamp), '', 'YYYY-MM-DD HH:mm:ss')}` : '';
|
||||
|
||||
/** 取一组历史步骤里最后一步(最近一次)的时间戳,用于组头部展示。 */
|
||||
export const groupTimestamp = (group: { steps: { step: { timestamp?: number } }[] }): number | undefined =>
|
||||
group.steps[group.steps.length - 1]?.step.timestamp;
|
||||
|
||||
export const opLabel = (op: HistoryOpType) => {
|
||||
switch (op) {
|
||||
case 'add':
|
||||
|
||||
@ -254,6 +254,7 @@ class History extends BaseService {
|
||||
public push(state: StepValue, pageId?: Id): StepValue | null {
|
||||
const undoRedo = this.getUndoRedo(pageId);
|
||||
if (!undoRedo) return null;
|
||||
if (state.timestamp === undefined) state.timestamp = Date.now();
|
||||
undoRedo.pushElement(state);
|
||||
// 仅当推入的是当前活动页时才需要刷新 canUndo/canRedo —— 其它页栈对当前 UI 状态没影响。
|
||||
if (pageId === undefined || `${pageId}` === `${this.state.pageId}`) {
|
||||
@ -289,6 +290,7 @@ class History extends BaseService {
|
||||
newContent: payload.newContent ? cloneDeep(payload.newContent) : null,
|
||||
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
||||
historyDescription: payload.historyDescription,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
this.getCodeBlockUndoRedo(codeBlockId).pushElement(step);
|
||||
@ -318,6 +320,7 @@ class History extends BaseService {
|
||||
newSchema: payload.newSchema ? cloneDeep(payload.newSchema) : null,
|
||||
changeRecords: payload.changeRecords?.length ? cloneDeep(payload.changeRecords) : undefined,
|
||||
historyDescription: payload.historyDescription,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
this.getDataSourceUndoRedo(dataSourceId).pushElement(step);
|
||||
|
||||
@ -222,6 +222,16 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 操作时间:弱化展示,紧贴在描述之后、各操作按钮之前。
|
||||
.m-editor-history-list-item-time {
|
||||
flex: 0 0 auto;
|
||||
color: #a8abb2;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 400; // 防止被合并组头部的粗体继承
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m-editor-history-list-item-op {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 6px;
|
||||
@ -387,6 +397,17 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m-editor-history-diff-dialog-notice {
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: #fdf6ec;
|
||||
border: 1px solid #faecd8;
|
||||
border-radius: 4px;
|
||||
color: #e6a23c;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.m-editor-history-diff-dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -708,6 +708,10 @@ export interface StepValue {
|
||||
* 不影响 undo/redo 行为;缺省时面板会根据节点 / propPath 自动生成描述。
|
||||
*/
|
||||
historyDescription?: string;
|
||||
/**
|
||||
* 入栈时间戳(毫秒)。在 historyService.push 时自动写入(若调用方未指定),仅用于历史面板展示。
|
||||
*/
|
||||
timestamp?: number;
|
||||
}
|
||||
// #endregion StepValue
|
||||
|
||||
@ -732,6 +736,8 @@ export interface CodeBlockStepValue {
|
||||
changeRecords?: ChangeRecord[];
|
||||
/** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
|
||||
historyDescription?: string;
|
||||
/** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
|
||||
timestamp?: number;
|
||||
}
|
||||
// #endregion CodeBlockStepValue
|
||||
|
||||
@ -756,6 +762,8 @@ export interface DataSourceStepValue {
|
||||
changeRecords?: ChangeRecord[];
|
||||
/** 调用方可选传入的人类可读描述,用于历史面板展示;不影响 undo/redo 行为。 */
|
||||
historyDescription?: string;
|
||||
/** 入栈时间戳(毫秒),入栈时自动写入,仅用于历史面板展示。 */
|
||||
timestamp?: number;
|
||||
}
|
||||
// #endregion DataSourceStepValue
|
||||
|
||||
|
||||
@ -56,6 +56,45 @@ describe('GroupRow.vue', () => {
|
||||
expect(wrapper.find('.m-editor-history-list-item-merge').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('传入 time 时头部渲染时间,title 取 timeTitle', () => {
|
||||
const wrapper = mount(GroupRow, {
|
||||
props: { ...baseProps, time: '12:00:00', timeTitle: '2026-06-03 12:00:00' },
|
||||
});
|
||||
const time = wrapper.find('.m-editor-history-list-item-time');
|
||||
expect(time.exists()).toBe(true);
|
||||
expect(time.text()).toBe('12:00:00');
|
||||
expect(time.attributes('title')).toBe('2026-06-03 12:00:00');
|
||||
});
|
||||
|
||||
test('未传 time 时头部不渲染时间元素', () => {
|
||||
const wrapper = mount(GroupRow, { props: baseProps });
|
||||
expect(wrapper.find('.m-editor-history-list-item-time').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('timeTitle 缺省时 title 回退为 time 本身', () => {
|
||||
const wrapper = mount(GroupRow, { props: { ...baseProps, time: '08:30:00' } });
|
||||
expect(wrapper.find('.m-editor-history-list-item-time').attributes('title')).toBe('08:30:00');
|
||||
});
|
||||
|
||||
test('展开的子步各自渲染自己的时间', () => {
|
||||
const wrapper = mount(GroupRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
merged: true,
|
||||
stepCount: 2,
|
||||
expanded: true,
|
||||
subSteps: [
|
||||
{ index: 0, applied: true, desc: '修改 颜色', time: '10:00:00', timeTitle: '2026-06-03 10:00:00' },
|
||||
{ index: 1, applied: true, desc: '修改 字号', time: '10:01:00', timeTitle: '2026-06-03 10:01:00' },
|
||||
],
|
||||
},
|
||||
});
|
||||
const items = wrapper.findAll('.m-editor-history-list-substeps li');
|
||||
// 子步倒序渲染:index=1 在前
|
||||
expect(items[0].find('.m-editor-history-list-item-time').text()).toBe('10:01:00');
|
||||
expect(items[1].find('.m-editor-history-list-item-time').text()).toBe('10:00:00');
|
||||
});
|
||||
|
||||
test('merged=true 且 expanded=true 时渲染子步列表', () => {
|
||||
const wrapper = mount(GroupRow, {
|
||||
props: {
|
||||
|
||||
@ -13,7 +13,7 @@ vi.mock('@tmagic/design', () => ({
|
||||
// 受控对话框:modelValue 为真时才渲染 body / footer 插槽
|
||||
TMagicDialog: defineComponent({
|
||||
name: 'TMagicDialog',
|
||||
props: ['modelValue'],
|
||||
props: ['modelValue', 'title'],
|
||||
setup(props, { slots }) {
|
||||
return () =>
|
||||
props.modelValue ? h('div', { class: 'fake-dialog' }, [slots.default?.(), slots.footer?.()]) : null;
|
||||
@ -207,6 +207,34 @@ describe('HistoryDiffDialog.vue', () => {
|
||||
expect(form.props('lastValue')).toEqual({ text: 'old' });
|
||||
});
|
||||
|
||||
test('无 onConfirm 时标题为「查看修改差异」', async () => {
|
||||
const wrapper = factory();
|
||||
(wrapper.vm as any).open(basePayload());
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TMagicDialog' }).props('title')).toBe('查看修改差异');
|
||||
});
|
||||
|
||||
test('有 onConfirm 时标题为「确认回滚」并展示回滚说明', async () => {
|
||||
const wrapper = mount(HistoryDiffDialog, {
|
||||
global: { stubs: { teleport: true } },
|
||||
props: { onConfirm: vi.fn() },
|
||||
});
|
||||
(wrapper.vm as any).open(basePayload());
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ name: 'TMagicDialog' }).props('title')).toBe('确认回滚');
|
||||
expect(wrapper.find('.m-editor-history-diff-dialog-notice').text()).toBe('仅回滚有差异的字段');
|
||||
});
|
||||
|
||||
test('无 onConfirm 时不展示回滚说明', async () => {
|
||||
const wrapper = factory();
|
||||
(wrapper.vm as any).open(basePayload());
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find('.m-editor-history-diff-dialog-notice').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('close() 隐藏对话框并清空 payload', async () => {
|
||||
const wrapper = factory();
|
||||
(wrapper.vm as any).open(basePayload());
|
||||
|
||||
@ -77,6 +77,21 @@ describe('PageTab.vue', () => {
|
||||
expect(rows[1].find('.m-editor-history-list-item-desc').text()).toBe('修改 按钮 (id: btn) · style.color');
|
||||
});
|
||||
|
||||
test('step 含 timestamp 时渲染时间元素', () => {
|
||||
const list = [buildPageGroup('add', [{ opType: 'add', nodes: [{ id: 'n1', name: 'A' }], timestamp: Date.now() }])];
|
||||
const wrapper = mount(PageTab, { props: { list, expanded: {} } });
|
||||
const time = wrapper.find('.m-editor-history-list-item-time');
|
||||
expect(time.exists()).toBe(true);
|
||||
// 当天记录展示 HH:mm:ss
|
||||
expect(time.text()).toMatch(/^\d{2}:\d{2}:\d{2}$/);
|
||||
});
|
||||
|
||||
test('step 无 timestamp 时不渲染时间元素', () => {
|
||||
const list = [buildPageGroup('add', [{ opType: 'add', nodes: [{ id: 'n1', name: 'A' }] }])];
|
||||
const wrapper = mount(PageTab, { props: { list, expanded: {} } });
|
||||
expect(wrapper.find('.m-editor-history-list-item-time').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('expanded 控制合并组的展开状态(key=pg-${idx})', async () => {
|
||||
const mergedGroup = buildPageGroup(
|
||||
'update',
|
||||
|
||||
@ -14,6 +14,9 @@ import {
|
||||
describeDataSourceStep,
|
||||
describePageGroup,
|
||||
describePageStep,
|
||||
formatHistoryFullTime,
|
||||
formatHistoryTime,
|
||||
groupTimestamp,
|
||||
opLabel,
|
||||
useHistoryList,
|
||||
} from '@editor/layouts/history-list/composables';
|
||||
@ -50,6 +53,50 @@ describe('opLabel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatHistoryFullTime', () => {
|
||||
test('无时间戳返回空串', () => {
|
||||
expect(formatHistoryFullTime()).toBe('');
|
||||
expect(formatHistoryFullTime(0)).toBe('');
|
||||
});
|
||||
|
||||
test('格式化为北京时间的完整 YYYY-MM-DD HH:mm:ss(不随本地时区漂移)', () => {
|
||||
// 2026-01-02 03:04:05 UTC → 北京时间 (UTC+8) 2026-01-02 11:04:05
|
||||
const ts = Date.UTC(2026, 0, 2, 3, 4, 5);
|
||||
expect(formatHistoryFullTime(ts)).toBe('2026-01-02 11:04:05');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatHistoryTime', () => {
|
||||
test('无时间戳返回空串', () => {
|
||||
expect(formatHistoryTime()).toBe('');
|
||||
expect(formatHistoryTime(0)).toBe('');
|
||||
});
|
||||
|
||||
test('当天记录只显示 HH:mm:ss', () => {
|
||||
expect(formatHistoryTime(Date.now())).toMatch(/^\d{2}:\d{2}:\d{2}$/);
|
||||
});
|
||||
|
||||
test('跨天记录显示 MM-DD HH:mm:ss', () => {
|
||||
// 取一个明显不是今天的旧时间戳
|
||||
const ts = Date.UTC(2020, 5, 15, 1, 2, 3);
|
||||
expect(formatHistoryTime(ts)).toMatch(/^\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('groupTimestamp', () => {
|
||||
test('取组内最后一步的时间戳', () => {
|
||||
const group = {
|
||||
steps: [{ step: { timestamp: 100 } }, { step: { timestamp: 200 } }, { step: { timestamp: 300 } }],
|
||||
};
|
||||
expect(groupTimestamp(group)).toBe(300);
|
||||
});
|
||||
|
||||
test('末步无时间戳时返回 undefined', () => {
|
||||
expect(groupTimestamp({ steps: [{ step: {} }] })).toBeUndefined();
|
||||
expect(groupTimestamp({ steps: [] })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('describePageStep', () => {
|
||||
test('显式 historyDescription 优先于自动生成', () => {
|
||||
const step = { opType: 'update', historyDescription: '调整按钮颜色' } as unknown as StepValue;
|
||||
|
||||
@ -84,6 +84,25 @@ describe('history service', () => {
|
||||
expect((history.state.pageSteps as any).p1.canUndo()).toBe(true);
|
||||
expect(history.state.canUndo).toBe(true);
|
||||
});
|
||||
|
||||
test('push 未带 timestamp 时自动写入入栈时间', () => {
|
||||
history.changePage({ id: 'p1' } as any);
|
||||
const before = Date.now();
|
||||
const step = history.push({ data: { id: 'p1', name: '' }, modifiedNodeIds: new Map() } as any);
|
||||
const after = Date.now();
|
||||
expect(step?.timestamp).toBeGreaterThanOrEqual(before);
|
||||
expect(step?.timestamp).toBeLessThanOrEqual(after);
|
||||
});
|
||||
|
||||
test('push 已带 timestamp 时保留调用方指定的值', () => {
|
||||
history.changePage({ id: 'p1' } as any);
|
||||
const step = history.push({
|
||||
data: { id: 'p1', name: '' },
|
||||
modifiedNodeIds: new Map(),
|
||||
timestamp: 123456,
|
||||
} as any);
|
||||
expect(step?.timestamp).toBe(123456);
|
||||
});
|
||||
});
|
||||
|
||||
describe('history service - codeBlock', () => {
|
||||
@ -111,6 +130,14 @@ describe('history service - codeBlock', () => {
|
||||
expect(history.pushCodeBlock('', { oldContent: null, newContent: null })).toBeNull();
|
||||
});
|
||||
|
||||
test('pushCodeBlock 自动写入入栈时间戳', () => {
|
||||
const before = Date.now();
|
||||
const step = history.pushCodeBlock('code_1', { oldContent: null, newContent: { name: 'A' } as any });
|
||||
const after = Date.now();
|
||||
expect(step?.timestamp).toBeGreaterThanOrEqual(before);
|
||||
expect(step?.timestamp).toBeLessThanOrEqual(after);
|
||||
});
|
||||
|
||||
test('undoCodeBlock / redoCodeBlock 走对应 id 的 UndoRedo 栈', () => {
|
||||
history.pushCodeBlock('code_1', { oldContent: null, newContent: { name: 'A' } as any });
|
||||
history.pushCodeBlock('code_1', {
|
||||
@ -183,6 +210,14 @@ describe('history service - dataSource', () => {
|
||||
expect(history.pushDataSource('', { oldSchema: null, newSchema: null })).toBeNull();
|
||||
});
|
||||
|
||||
test('pushDataSource 自动写入入栈时间戳', () => {
|
||||
const before = Date.now();
|
||||
const step = history.pushDataSource('ds_1', { oldSchema: null, newSchema: { id: 'ds_1' } as any });
|
||||
const after = Date.now();
|
||||
expect(step?.timestamp).toBeGreaterThanOrEqual(before);
|
||||
expect(step?.timestamp).toBeLessThanOrEqual(after);
|
||||
});
|
||||
|
||||
test('undoDataSource / redoDataSource 走对应 id 的 UndoRedo 栈', () => {
|
||||
history.pushDataSource('ds_1', {
|
||||
oldSchema: null,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user