@@ -1634,3 +1634,112 @@ TEST_F(Engraving_PartsTests, saveLightweightExcerpt)
16341634 // Cleanup - comment out for debugging
16351635 std::filesystem::remove (std::filesystem::path (tempFile.toStdString ()));
16361636}
1637+
1638+ // ---------------------------------------------------------
1639+ // lightweightExcerptAfterInitDeinit
1640+ // / Test that lightweight excerpts remain lightweight after being
1641+ // / temporarily initialized (e.g., for export) and then deinitialized.
1642+ // / This simulates the export flow where excerpts are initialized for
1643+ // / rendering but should return to lightweight state after export.
1644+ // / Related: https://github.com/musescore/MuseScore/issues/31656
1645+ // ---------------------------------------------------------
1646+
1647+ TEST_F (Engraving_PartsTests, lightweightExcerptAfterInitDeinit)
1648+ {
1649+ using namespace muse ::io;
1650+ using namespace mu ::engraving::rw;
1651+
1652+ // Load a score
1653+ MasterScore* score = ScoreRW::readScore (PARTS_DATA_DIR + u" part-all.mscx" );
1654+ ASSERT_TRUE (score);
1655+
1656+ // Create a lightweight excerpt
1657+ Excerpt* excerpt = new Excerpt (score);
1658+ Part* part = score->parts ().front ();
1659+ excerpt->parts ().push_back (part);
1660+ excerpt->setInitialPartId (part->id ());
1661+
1662+ String customName = u" Init Deinit Test Part" ;
1663+ excerpt->setName (customName, false );
1664+
1665+ // Add as lightweight excerpt
1666+ score->addLightweightExcerpt (excerpt);
1667+
1668+ // Verify it's lightweight
1669+ ASSERT_EQ (excerpt->excerptScore (), nullptr ) << " Excerpt should start as lightweight" ;
1670+
1671+ // Initialize the excerpt (simulates what export does)
1672+ score->initExcerpt (excerpt);
1673+ ASSERT_NE (excerpt->excerptScore (), nullptr ) << " Excerpt should be initialized after initExcerpt" ;
1674+ EXPECT_TRUE (excerpt->inited ()) << " Excerpt should be marked as inited" ;
1675+
1676+ // Deinitialize the excerpt (simulates what happens after export)
1677+ Score* excerptScore = excerpt->excerptScore ();
1678+ excerpt->setExcerptScore (nullptr );
1679+ excerpt->setInited (false );
1680+ delete excerptScore;
1681+
1682+ // Verify it's back to lightweight
1683+ ASSERT_EQ (excerpt->excerptScore (), nullptr ) << " Excerpt should be lightweight after deinit" ;
1684+ EXPECT_FALSE (excerpt->inited ()) << " Excerpt should not be marked as inited after deinit" ;
1685+
1686+ // Save to MSCZ
1687+ String tempFile = String::fromStdString (
1688+ (std::filesystem::temp_directory_path () / " init-deinit-test.mscz" ).string ());
1689+
1690+ if (File::exists (tempFile)) {
1691+ File::remove (tempFile);
1692+ }
1693+
1694+ {
1695+ MscWriter::Params writerParams;
1696+ writerParams.filePath = tempFile;
1697+ writerParams.mode = MscIoMode::Zip;
1698+
1699+ MscWriter mscWriter (writerParams);
1700+ ASSERT_TRUE (mscWriter.open ()) << " Failed to open MscWriter" ;
1701+
1702+ MscSaver saver (score->iocContext ());
1703+ bool saveOk = saver.writeMscz (score, mscWriter, false );
1704+ ASSERT_TRUE (saveOk) << " Failed to save score" ;
1705+ }
1706+
1707+ delete score;
1708+
1709+ // Verify the saved file has lightweight excerpt
1710+ {
1711+ MscReader::Params readerParams;
1712+ readerParams.filePath = tempFile;
1713+ readerParams.mode = MscIoMode::Zip;
1714+
1715+ MscReader mscReader (readerParams);
1716+ ASSERT_TRUE (mscReader.open ()) << " Failed to open MscReader" ;
1717+
1718+ std::vector<String> excerptFiles = mscReader.excerptFileNames ();
1719+ ASSERT_EQ (excerptFiles.size (), 1u ) << " Should have one excerpt file" ;
1720+
1721+ ByteArray excerptData = mscReader.readExcerptFile (excerptFiles.front ());
1722+ ASSERT_FALSE (excerptData.empty ()) << " Excerpt file should exist" ;
1723+
1724+ String excerptXml = String::fromUtf8 (excerptData);
1725+
1726+ // Should be lightweight (small size, lightweight marker, no full content)
1727+ EXPECT_TRUE (excerptXml.contains (u" <lightweight>" )) << " Should have lightweight marker" ;
1728+ EXPECT_FALSE (excerptXml.contains (u" <Staff>" )) << " Should not have Staff element" ;
1729+ EXPECT_FALSE (excerptXml.contains (u" <Measure>" )) << " Should not have Measure element" ;
1730+ EXPECT_LT (excerptData.size (), 1024u ) << " Lightweight excerpt should be small (< 1KB)" ;
1731+ }
1732+
1733+ // Reload and verify
1734+ MasterScore* reloadedScore = ScoreRW::readScore (tempFile, true );
1735+ ASSERT_TRUE (reloadedScore) << " Failed to reload score" ;
1736+
1737+ ASSERT_EQ (reloadedScore->excerpts ().size (), 1u ) << " Should have one excerpt" ;
1738+ Excerpt* loadedExcerpt = reloadedScore->excerpts ().front ();
1739+
1740+ EXPECT_EQ (loadedExcerpt->name (), customName) << " Name should be preserved" ;
1741+ EXPECT_EQ (loadedExcerpt->excerptScore (), nullptr ) << " Should still be lightweight after reload" ;
1742+
1743+ delete reloadedScore;
1744+ std::filesystem::remove (std::filesystem::path (tempFile.toStdString ()));
1745+ }
0 commit comments