/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <swmodeltestbase.hxx>

#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/propertyvalue.hxx>

#include <IDocumentRedlineAccess.hxx>
#include <docsh.hxx>
#include <redline.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <swmodule.hxx>
#include <strings.hrc>
#include <fchrfmt.hxx>
#include <ndtxt.hxx>

using namespace css;
using namespace css::uno;

namespace
{
/// Covers sw/source/core/doc/DocumentRedlineManager.cxx fixes.
class Test : public SwModelTestBase
{
public:
    Test()
        : SwModelTestBase(u"/sw/qa/core/doc/data/"_ustr)
    {
    }
};

CPPUNIT_TEST_FIXTURE(Test, testRedlineIns)
{
    // Given a document with an insert redline:
    createSwDoc("ins.docx");

    // When selecting BBB (a subset of the insert) and marking that as bold:
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 3, /*bBasicCall=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);
    SwView& rView = pWrtShell->GetView();
    {
        SvxWeightItem aWeightItem(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aWeightItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // Then make sure BBB is covered by an insert-then-format redline:
    SwDoc* pDoc = getSwDocShell()->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    {
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: 1
        // - Actual  : 3
        // i.e. no redline was created for the format change.
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[0]->GetType());
        const SwRedlineData& rRedlineData1 = rRedlines[1]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData1.GetType());
        CPPUNIT_ASSERT(rRedlineData1.Next());
        const SwRedlineData& rInnerRedlineData = *rRedlineData1.Next();
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rInnerRedlineData.GetType());
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[2]->GetType());
    }

    // And when undoing:
    pWrtShell->Undo();

    // Then make sure we again have a single insert that covers all text:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 2
    // i.e. the insert redline of BBB was lost on undo.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[0]->GetType());
    CPPUNIT_ASSERT_EQUAL(u"AAABBBCCC"_ustr, rRedlines[0]->GetText());

    // And when redoing:
    pWrtShell->Redo();

    // Then make sure we get <ins>AAA<format>BBB</format>CCC</ins>:
    {
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[0]->GetType());
        const SwRedlineData& rRedlineData1 = rRedlines[1]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData1.GetType());
        // Without the accompanying fix in place, this test would have failed, the "format" redline
        // has no underlying "insert" redline.
        CPPUNIT_ASSERT(rRedlineData1.Next());
        const SwRedlineData& rInnerRedlineData = *rRedlineData1.Next();
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rInnerRedlineData.GetType());
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlines[2]->GetType());
    }
}

CPPUNIT_TEST_FIXTURE(Test, testHighlightOnInsContext)
{
    // Given a document with "AAABBBCCC", where BBB is an insert redline by Alice:
    createSwDoc("ins-with-context.docx");
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();

    // When selecting ABBBC (the last A of "AAA", BBB, the first C of "CCC") and highlighting
    // it as yellow:
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 5, /*bBasicCall=*/false);
    cpo::uno::Sequence<beans::PropertyValue> aArgs = {
        comphelper::makePropertyValue(u"CharBackColor"_ustr,
                                      cpo::uno::Any(static_cast<sal_Int32>(0xffff00))),
    };
    dispatchCommand(mxComponent, u".uno:CharBackColor"_ustr, aArgs);

    // Then make sure we get format(A) + format-on-insert(BBB) + format(C), so the underlying
    // insert is preserved:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 3
    // - Actual  : 1
    // i.e. we got a format redline, but lost the insert redline.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());

    // And when rejecting the first redline (the format on the last A of "AAA"):
    pWrtShell->RejectRedline(0, /*bDirect=*/false);

    // Then make sure the highlight is gone from that A:
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, /*bBasicCall=*/false);
    SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END - 1> aCharSet(pDoc->GetAttrPool());
    pWrtShell->GetCurAttr(aCharSet);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 32 (SfxItemState::DEFAULT)
    // - Actual  : 64 (SfxItemState::SET)
    // i.e. the brush item survived the reject.
    CPPUNIT_ASSERT_EQUAL(SfxItemState::DEFAULT,
                         aCharSet.GetItemState(RES_CHRATR_BACKGROUND, false));
}

CPPUNIT_TEST_FIXTURE(Test, testHideChangesFormatOnDelete)
{
    // Given a document with a format-on-delete hierarchical redline (the deleted text has a format
    // redline on top of the delete redline):
    createSwDoc("del-then-format.docx");

    // When hiding tracked changes:
    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});

    // Then make sure the deleted text is also omitted from the layout, just like a plain delete
    // redline would be:
    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
    OUString aPortion = getXPath(pXmlDoc, "//body/txt/SwParaPortion/SwLineLayout", "portion");
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected:
    // - Actual  : BBB
    // i.e. only delete was hidden, delete-then-format was still visible.
    CPPUNIT_ASSERT_EQUAL(u""_ustr, aPortion);
}

CPPUNIT_TEST_FIXTURE(Test, testInsThenFormatSelf)
{
    // Given a document with <ins>A<format>B</format>C</ins> redlines, created by Alice:
    createSwDoc("ins-then-format-self.docx");
    SwModule* pModule = SwModule::get();
    pModule->SetRedlineAuthor(u"Alice"_ustr);
    comphelper::ScopeGuard g(
        [pModule] { pModule->SetRedlineAuthor(SwResId(STR_REDLINE_UNKNOWN_AUTHOR)); });
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/false);
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());
    pWrtShell->DelLeft();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rRedlines.size());

    // When deleting B:
    pWrtShell->DelLeft();

    // Then make sure that B is removed from the document (since this is a self-insert):
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 2
    // i.e. a delete was created instead of removing the insert.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
}

CPPUNIT_TEST_FIXTURE(Test, testFormatRedlineRecordOldCharStyle)
{
    // Given a document with one char style applied + redline record on:
    createSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"x"_ustr);
    pWrtShell->SelAll();
    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues = {
        comphelper::makePropertyValue(u"Style"_ustr, cpo::uno::Any(u"Emphasis"_ustr)),
        comphelper::makePropertyValue(u"FamilyName"_ustr, cpo::uno::Any(u"CharacterStyles"_ustr)),
    };
    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr, aPropertyValues);
    pDocShell->SetChangeRecording(true);

    // When changing to a second char style:
    aPropertyValues = {
        comphelper::makePropertyValue(u"Style"_ustr, cpo::uno::Any(u"Strong Emphasis"_ustr)),
        comphelper::makePropertyValue(u"FamilyName"_ustr, cpo::uno::Any(u"CharacterStyles"_ustr)),
    };
    dispatchCommand(mxComponent, u".uno:StyleApply"_ustr, aPropertyValues);

    // Then make sure the redline refers to the old char style and just to that:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    const SwRangeRedline& rRedline = *rRedlines[0];
    CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedline.GetType());
    auto pExtraData = dynamic_cast<const SwRedlineExtraData_FormatColl*>(rRedline.GetExtraData());
    CPPUNIT_ASSERT(pExtraData);
    std::shared_ptr<SfxItemSet> pRedlineSet = pExtraData->GetItemSet();
    CPPUNIT_ASSERT(pRedlineSet);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 5
    // i.e. more than just the char style change was recorded, which was unexpected.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(1), pRedlineSet->Count());
    const SwFormatCharFormat& rOldCharFormat = pRedlineSet->Get(RES_TXTATR_CHARFMT);
    CPPUNIT_ASSERT_EQUAL(u"Emphasis"_ustr, rOldCharFormat.GetCharFormat()->GetName().toString());
}

CPPUNIT_TEST_FIXTURE(Test, testDelThenFormatDirect)
{
    // Given a document with a delete redline, part of it has a format redline on top:
    createSwDoc("del-then-format.docx");

    // When "directly" accepting the delete-then-format redline:
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->AcceptRedline(1, /*bDirect=*/true);

    // Then make sure that the format gets accepted, and the delete redline is kept:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    {
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: 1
        // - Actual  : 0
        // i.e. when ignoring the "direct" parameter, the delete redline was accepted instead of the
        // format one.
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());

        // After first char inside the redline: bold.
        pWrtShell->SttEndDoc(/*bStt=*/true);
        pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
        SfxItemSetFixed<RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT> aSet(pDoc->GetAttrPool());
        pWrtShell->GetCurAttr(aSet);
        const SvxWeightItem& rWeightItem = aSet.Get(RES_CHRATR_WEIGHT);
        CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, rWeightItem.GetValue());
    }

    // And given an undo, so we can redo:
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());

    // When doing the same again via redo:
    pWrtShell->Redo();

    // Then make sure we again have a single large delete:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 2
    // i.e. instead of a large delete redline, we have 2 small ones for AAA and CCC only.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());

    // And given a reset state:
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());

    // When "directly" rejecting the delete-then-format redline:
    pWrtShell->RejectRedline(1, /*bDirect=*/true);

    // Then make sure that the format gets rejected and the delete redline is kept:
    {
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: 1 (delete)
        // - Actual  : 2 (format)
        // i.e. the delete redline was rejected and format remained, not the other way around.
        CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());

        // After first char inside the redline: not bold anymore.
        pWrtShell->SttEndDoc(/*bStt=*/true);
        pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
        SfxItemSetFixed<RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT> aSet(pDoc->GetAttrPool());
        pWrtShell->GetCurAttr(aSet);
        const SvxWeightItem& rWeightItem = aSet.Get(RES_CHRATR_WEIGHT);
        CPPUNIT_ASSERT_EQUAL(WEIGHT_NORMAL, rWeightItem.GetValue());
    }

    // And given an undo:
    pWrtShell->Undo();

    // When redoing:
    pWrtShell->Redo();

    // Then make sure that we only get a single big delete redline:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 3
    // i.e. we got <del>AAA</del><format>BBB</format><del>CCC</del> instead of one big delete
    // redline.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
}

CPPUNIT_TEST_FIXTURE(Test, testInsThenFormatDirect)
{
    // Given a document with an insert redline, part of it has a format redline on top:
    createSwDoc("ins-then-format.docx");

    // When "directly" accepting the insert-then-format redline:
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->AcceptRedline(1, /*bDirect=*/true);

    // Then make sure that the format gets accepted, and the insert redline is kept:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    {
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: 1
        // - Actual  : 3
        // i.e. the accept didn't do anything in direct mode.
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());

        // After first char inside the redline: bold.
        pWrtShell->SttEndDoc(/*bStt=*/true);
        pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
        SfxItemSetFixed<RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT> aSet(pDoc->GetAttrPool());
        pWrtShell->GetCurAttr(aSet);
        const SvxWeightItem& rWeightItem = aSet.Get(RES_CHRATR_WEIGHT);
        CPPUNIT_ASSERT_EQUAL(WEIGHT_BOLD, rWeightItem.GetValue());
    }

    // And given an undo, so we can redo:
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());

    // When doing the same again via redo:
    pWrtShell->Redo();

    // Then make sure we again have a single large insert:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 3
    // i.e. redo was broken, the middle part was a format redline instead of an insert redline.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());

    // And given a reset state:
    pWrtShell->Undo();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());

    // When "directly" rejecting the insert-then-format redline:
    pWrtShell->RejectRedline(1, /*bDirect=*/true);

    // Then make sure that the format gets rejected and the insert redline is kept:
    {
        SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode();
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: AAABBBCCC
        // - Actual  : AAACCC
        // i.e. the insert was rejected, not the format.
        CPPUNIT_ASSERT_EQUAL(u"AAABBBCCC"_ustr, pTextNode->GetText());
        CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Insert, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());

        // After first char inside the redline: not bold anymore.
        pWrtShell->SttEndDoc(/*bStt=*/true);
        pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
        SfxItemSetFixed<RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT> aSet(pDoc->GetAttrPool());
        pWrtShell->GetCurAttr(aSet);
        const SvxWeightItem& rWeightItem = aSet.Get(RES_CHRATR_WEIGHT);
        CPPUNIT_ASSERT_EQUAL(WEIGHT_NORMAL, rWeightItem.GetValue());
    }

    // And given an undo:
    pWrtShell->Undo();

    // When redoing:
    pWrtShell->Redo();

    // Then make sure that we only get a single big insert redline:
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 2
    // i.e. we got <ins>AAA</ins>BBB<ins>CCC</ins> instead of one big insert.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
}

CPPUNIT_TEST_FIXTURE(Test, testDelThenFormatOwn)
{
    // Given a document with an overlapping delete and format redline:
    // When importing that document:
    createSwDoc("del-then-format-own.docx");

    // Then make sure that the overlap part is not lost, instead it's represented with an
    // SwRangeRedline with 2 SwRedlineData members:
    SwDocShell* pDocShell = getSwDocShell();
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 3
    // - Actual  : 2
    // i.e. the document had a format and a delete redline, but part of the format was lost.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());
    {
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());
    }
    {
        // Overlap: both format and delete is tracked, so reject removes all boldness from the
        // document.
        const SwRedlineData& rRedlineData = rRedlines[1]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
        CPPUNIT_ASSERT(rRedlineData.Next());
        const SwRedlineData& rRedlineData2 = rRedlines[1]->GetRedlineData(1);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData2.GetType());
        CPPUNIT_ASSERT(!rRedlineData2.Next());
    }
    {
        const SwRedlineData& rRedlineData = rRedlines[2]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());
    }
}

CPPUNIT_TEST_FIXTURE(Test, testFormatThenDel)
{
    // Given an "AAA <format>BBB CCC DDD</format> EEE" document:
    createSwDoc("fmt.docx");
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->SttEndDoc(/*bStt=*/true);
    // Skip "AAA BBB ".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 8, /*bBasicCall=*/false);
    // Select "CCC".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);

    // When deleting CCC:
    pWrtShell->DelLeft();

    // Then make sure the resulting new "delete" redline still tracks formatting:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rRedlines.size());
    {
        const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());
    }
    {
        const SwRedlineData& rRedlineData = rRedlines[1]->GetRedlineData(0);
        // Without the accompanying fix in place, this test would have failed with:
        // - Expected: 2 (Format)
        // - Actual  : 1 (Delete)
        // i.e. the middle redline was just "delete", not "format-on-delete", so formatting remained
        // in the document after reject-all.
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
        CPPUNIT_ASSERT(rRedlineData.Next());
        const SwRedlineData& rRedlineData2 = rRedlines[1]->GetRedlineData(1);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData2.GetType());
        CPPUNIT_ASSERT(!rRedlineData2.Next());
    }
    {
        const SwRedlineData& rRedlineData = rRedlines[2]->GetRedlineData(0);
        CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
        CPPUNIT_ASSERT(!rRedlineData.Next());
    }
}

CPPUNIT_TEST_FIXTURE(Test, testDelThenFormatFormat)
{
    // Given a document with "test" that has a delete redline and a bold
    // format redline applied on top of it:
    createSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"test"_ustr);
    pDocShell->SetChangeRecording(true);
    SwView& rView = pWrtShell->GetView();
    pWrtShell->SelAll();
    pWrtShell->DelLeft();
    pWrtShell->SelAll();
    {
        SvxWeightItem aWeightItem(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aWeightItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // When applying an additional italic format on the same range:
    {
        SvxPostureItem aPostureItem(ITALIC_NORMAL, RES_CHRATR_POSTURE);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aPostureItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // Then make sure the delete-then-format stack is kept unchanged, instead
    // of being replaced with a single plain format redline:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
    CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
    // Without the accompanying fix in place, this test would have failed here,
    // i.e. the new italic redline destroyed the existing format-on-delete
    // redline, losing the underlying delete redline.
    CPPUNIT_ASSERT(rRedlineData.Next());
    const SwRedlineData& rRedlineData2 = *rRedlineData.Next();
    CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData2.GetType());
    CPPUNIT_ASSERT(!rRedlineData2.Next());
}

CPPUNIT_TEST_FIXTURE(Test, testDelThenFormatFormatInside)
{
    // Given "AAA BBB CCC" with a delete redline and a bold format redline
    // applied on top of the full range:
    createSwDoc();
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Insert(u"AAA BBB CCC"_ustr);
    pDocShell->SetChangeRecording(true);
    SwView& rView = pWrtShell->GetView();
    pWrtShell->SelAll();
    pWrtShell->DelLeft();
    pWrtShell->SelAll();
    {
        SvxWeightItem aWeightItem(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aWeightItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // When applying an additional italic format on only the middle word:
    pWrtShell->SttEndDoc(/*bStt=*/true);
    // Skip "AAA ".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    // Select "BBB".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);
    {
        SvxPostureItem aPostureItem(ITALIC_NORMAL, RES_CHRATR_POSTURE);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aPostureItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // Then make sure the delete-then-format redline is kept unchanged, instead
    // of being replaced with a single plain format redline for BBB:
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 1
    // - Actual  : 3
    // i.e. we got a format-then-delete, a format and a format-on-delete redline instead of a single
    // format-on-delete redline.
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    CPPUNIT_ASSERT_EQUAL(u"AAA BBB CCC"_ustr, rRedlines[0]->GetText());
    const SwRedlineData& rRedlineData = rRedlines[0]->GetRedlineData(0);
    CPPUNIT_ASSERT_EQUAL(RedlineType::Format, rRedlineData.GetType());
    CPPUNIT_ASSERT(rRedlineData.Next());
    const SwRedlineData& rRedlineData2 = *rRedlineData.Next();
    CPPUNIT_ASSERT_EQUAL(RedlineType::Delete, rRedlineData2.GetType());
    CPPUNIT_ASSERT(!rRedlineData2.Next());
}

CPPUNIT_TEST_FIXTURE(Test, testFormatThenFormatOther)
{
    // Given an "AAA <format>BBB CCC DDD</format> EEE" document with a bold
    // format redline authored by Alice:
    createSwDoc("fmt.docx");
    SwDocShell* pDocShell = getSwDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    SwDoc* pDoc = pDocShell->GetDoc();
    IDocumentRedlineAccess& rIDRA = pDoc->getIDocumentRedlineAccess();
    SwRedlineTable& rRedlines = rIDRA.GetRedlineTable();
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    CPPUNIT_ASSERT_EQUAL(u"Alice"_ustr, rRedlines[0]->GetAuthorString());

    // When a different author selects the same range and applies italic:
    SwModule* pModule = SwModule::get();
    pModule->SetRedlineAuthor(u"Bob"_ustr);
    comphelper::ScopeGuard g(
        [pModule] { pModule->SetRedlineAuthor(SwResId(STR_REDLINE_UNKNOWN_AUTHOR)); });
    pWrtShell->SttEndDoc(/*bStt=*/true);
    // Skip "AAA ".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, /*bBasicCall=*/false);
    // Select "BBB CCC DDD".
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 11, /*bBasicCall=*/false);
    SwView& rView = pWrtShell->GetView();
    {
        SvxPostureItem aPostureItem(ITALIC_NORMAL, RES_CHRATR_POSTURE);
        SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END> aSet(rView.GetPool());
        aSet.Put(aPostureItem);
        pWrtShell->SetAttrSet(aSet);
    }

    // Then make sure the only surviving redline is now authored by Bob (the
    // current user), not Alice:
    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
    CPPUNIT_ASSERT_EQUAL(u"Bob"_ustr, rRedlines[0]->GetAuthorString());

    // And when rejecting that surviving redline:
    pWrtShell->RejectRedline(0, /*bDirect=*/false);

    // Then make sure both Alice's bold and Bob's italic are gone from
    // "BBB CCC DDD":
    pWrtShell->SttEndDoc(/*bStt=*/true);
    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false);
    SfxItemSetFixed<RES_CHRATR_POSTURE, RES_CHRATR_WEIGHT> aSet(pDoc->GetAttrPool());
    pWrtShell->GetCurAttr(aSet);
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: 5 (WEIGHT_NORMAL)
    // - Actual  : 8 (WEIGHT_BOLD)
    // i.e. the bold was in the document after reject, which is not correct.
    CPPUNIT_ASSERT_EQUAL(WEIGHT_NORMAL, aSet.Get(RES_CHRATR_WEIGHT).GetValue());
    CPPUNIT_ASSERT_EQUAL(ITALIC_NONE, aSet.Get(RES_CHRATR_POSTURE).GetValue());
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
