Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ export const addMarkStep = ({ state, step, newTr, doc, user, date }) => {
});
}
} else {
before = liveMarks
.filter((mark) => ![TrackDeleteMarkName, TrackFormatMarkName].includes(mark.type.name))
.map((mark) => ({
type: mark.type.name,
attrs: { ...mark.attrs },
}));
const existingMarkOfSameType = liveMarks.find(
(mark) =>
mark.type.name === step.mark.type.name &&
![TrackDeleteMarkName, TrackFormatMarkName].includes(mark.type.name),
);
before = existingMarkOfSameType
? [{ type: existingMarkOfSameType.type.name, attrs: { ...existingMarkOfSameType.attrs } }]
: [];

after = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,53 @@ describe('trackChangesHelpers', () => {
expect(meta?.formatMark?.attrs?.after).toEqual([{ type: 'highlight', attrs: { color: '#E4668C' } }]);
});

it('addMarkStep does not include unrelated marks in before (SD-2077)', () => {
const highlight = schema.marks.highlight.create({ color: '#FFFF00' });
const doc = createDocWithText('Hello', [highlight]);
const state = createState(doc);
const boldMark = schema.marks.bold.create();
const step = new AddMarkStep(1, 6, boldMark);
const newTr = state.tr;

addMarkStep({
state,
step,
newTr,
doc: state.doc,
user,
date,
});

const meta = newTr.getMeta(TrackChangesBasePluginKey);
expect(meta?.formatMark?.type.name).toBe(TrackFormatMarkName);
expect(meta?.formatMark?.attrs?.before).toEqual([]);
expect(meta?.formatMark?.attrs?.after).toEqual([{ type: 'bold', attrs: boldMark.attrs }]);
});

it('addMarkStep only captures same-type mark in before when replacing (SD-2077)', () => {
const highlight = schema.marks.highlight.create({ color: '#FFFF00' });
const textStyle = schema.marks.textStyle.create({ color: '#112233', fontSize: '11pt' });
const doc = createDocWithText('Hello', [highlight, textStyle]);
const state = createState(doc);
const changedTextStyle = schema.marks.textStyle.create({ color: '#FF0000', fontSize: '11pt' });
const step = new AddMarkStep(1, 6, changedTextStyle);
const newTr = state.tr;

addMarkStep({
state,
step,
newTr,
doc: state.doc,
user,
date,
});

const meta = newTr.getMeta(TrackChangesBasePluginKey);
expect(meta?.formatMark?.type.name).toBe(TrackFormatMarkName);
expect(meta?.formatMark?.attrs?.before).toEqual([{ type: 'textStyle', attrs: textStyle.attrs }]);
expect(meta?.formatMark?.attrs?.after).toEqual([{ type: 'textStyle', attrs: changedTextStyle.attrs }]);
});

it('removeMarkStep records previous formatting when mark removed', () => {
const bold = schema.marks.bold.create();
const doc = createDocWithText('Styled', [bold]);
Expand Down
134 changes: 134 additions & 0 deletions tests/behavior/tests/comments/sd-2077-format-change-display.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { Page } from '@playwright/test';
import { test, expect } from '../../fixtures/superdoc.js';
import { assertDocumentApiReady } from '../../helpers/document-api.js';

test.use({ config: { toolbar: 'full', comments: 'on', trackChanges: true } });

type EditorCommand = [name: string, ...args: unknown[]];

async function runCommands(page: Page, commands: EditorCommand[]): Promise<void> {
for (const [name, ...args] of commands) {
await page.evaluate(({ name, args }) => (window as any).editor.commands[name](...args), { name, args });
}
}

async function expectTrackedFormatDialog(page: Page) {
const dialog = page.locator('.comment-placeholder .comments-dialog', {
has: page.locator('.tracked-change-text'),
});
await expect(dialog).toBeVisible({ timeout: 10_000 });
return dialog;
}

test.describe('SD-2077 tracked format change displays correct description', () => {
test('adding bold to highlighted text shows only bold addition', async ({ superdoc }) => {
await assertDocumentApiReady(superdoc.page);

// Type text and apply highlight in editing mode
await superdoc.type('Hello world');
await superdoc.waitForStable();
await superdoc.selectAll();
await runCommands(superdoc.page, [['setHighlight', '#FFFF00']]);
await superdoc.waitForStable();

// Switch to suggesting mode
await superdoc.setDocumentMode('suggesting');
await superdoc.waitForStable();

// Select text and apply bold
await superdoc.selectAll();
await superdoc.bold();
await superdoc.waitForStable();

// Verify tracked format change exists
await superdoc.assertTrackedChangeExists('format');

// Wait for the tracked change comment dialog to appear
const dialog = await expectTrackedFormatDialog(superdoc.page);

// The format description should mention bold but NOT mention highlight removal
const formatText = dialog.locator('.tracked-change-text');
await expect(formatText).toContainText('bold');

const text = await formatText.textContent();
expect(text).not.toContain('removed');
expect(text).not.toContain('highlight');

await superdoc.snapshot('sd-2077-bold-on-highlighted');
});

test('adding italic to bold text shows only italic addition', async ({ superdoc }) => {
await assertDocumentApiReady(superdoc.page);

// Type text and apply bold in editing mode
await superdoc.type('Hello world');
await superdoc.waitForStable();
await superdoc.selectAll();
await superdoc.bold();
await superdoc.waitForStable();

// Verify bold is applied
await superdoc.assertTextHasMarks('Hello', ['bold']);

// Switch to suggesting mode
await superdoc.setDocumentMode('suggesting');
await superdoc.waitForStable();

// Select text and apply italic
await superdoc.selectAll();
await superdoc.italic();
await superdoc.waitForStable();

// Verify tracked format change exists
await superdoc.assertTrackedChangeExists('format');

const dialog = await expectTrackedFormatDialog(superdoc.page);

// Should show italic addition, not bold removal
const formatText = dialog.locator('.tracked-change-text');
await expect(formatText).toContainText('italic');

const text = await formatText.textContent();
expect(text).not.toContain('removed');
expect(text).not.toContain('bold');

await superdoc.snapshot('sd-2077-italic-on-bold');
});

test('changing color on highlighted text shows only color change', async ({ superdoc }) => {
await assertDocumentApiReady(superdoc.page);

// Type text and apply highlight + color in editing mode
await superdoc.type('Hello world');
await superdoc.waitForStable();
await superdoc.selectAll();
await runCommands(superdoc.page, [
['setHighlight', '#FFFF00'],
['setColor', '#112233'],
]);
await superdoc.waitForStable();

// Switch to suggesting mode
await superdoc.setDocumentMode('suggesting');
await superdoc.waitForStable();

// Select text and change color
await superdoc.selectAll();
await runCommands(superdoc.page, [['setColor', '#FF0000']]);
await superdoc.waitForStable();

// Verify tracked format change exists
await superdoc.assertTrackedChangeExists('format');

const dialog = await expectTrackedFormatDialog(superdoc.page);

// Should show color change, not highlight removal
const formatText = dialog.locator('.tracked-change-text');
await expect(formatText).toContainText('color');

const text = await formatText.textContent();
expect(text).not.toContain('removed highlight');

await superdoc.snapshot('sd-2077-color-on-highlighted');
});
});
Loading