KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_symbol_editor_tabs.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
23
28
29#include <lib_symbol.h>
30#include <sch_screen.h>
31#include <undo_redo_container.h>
33
35
36#include <nlohmann/json.hpp>
37
38
41{
42public:
43 INSTRUMENTED_LIB_SYMBOL( const wxString& aName, int* aDtorCounter ) :
44 LIB_SYMBOL( aName ),
45 m_dtorCounter( aDtorCounter )
46 {
47 }
48
50 {
51 if( m_dtorCounter )
52 ( *m_dtorCounter )++;
53 }
54
55private:
57};
58
59
61{
63
65 std::unique_ptr<SYMBOL_BUFFER> makeBuffer( const wxString& aName )
66 {
67 auto symbol = std::make_unique<LIB_SYMBOL>( aName );
68 auto screen = std::make_unique<SCH_SCREEN>();
69
70 return std::make_unique<SYMBOL_BUFFER>( std::move( symbol ), std::move( screen ) );
71 }
72
75 {
76 aTransient->SetFlags( UR_TRANSIENT );
77
79 cmd->PushItem( ITEM_PICKER( nullptr, aTransient, UNDO_REDO::LIBEDIT ) );
80 aList.PushCommand( cmd );
81 }
82
84 static void freeTransientUndoCommands( UNDO_REDO_CONTAINER& aList, const LIB_SYMBOL* aLiveSymbol )
85 {
87 }
88
90 static bool libTreeAutoHiddenForSchematicEdit( bool aWasFromSchematic, bool aRestorePending,
91 bool aTreeShownNow )
92 {
94 aRestorePending, aTreeShownNow );
95 }
96};
97
98
99BOOST_FIXTURE_TEST_SUITE( SymbolEditorTabs, SYMBOL_EDITOR_TABS_TEST_FIXTURE )
100
101
102
104BOOST_AUTO_TEST_CASE( SymbolTabContextOwnsWorkingCopy )
105{
106 std::unique_ptr<SYMBOL_BUFFER> buf = makeBuffer( wxS( "R" ) );
107
108 SYMBOL_EDITOR_TAB_CONTEXT ctx( wxS( "Device" ), wxS( "R" ), buf.get() );
109
110 BOOST_CHECK_EQUAL( ctx.GetTabKey(), wxS( "Device:R" ) );
111 BOOST_CHECK_EQUAL( ctx.GetDisplayName(), wxS( "R" ) );
112
113 BOOST_REQUIRE( ctx.GetSymbol() != nullptr );
114 BOOST_REQUIRE( ctx.GetScreen() != nullptr );
115 BOOST_CHECK( ctx.GetSymbol() != &buf->GetSymbol() );
116 BOOST_CHECK( ctx.GetScreen() != buf->GetScreen() );
117 BOOST_CHECK_EQUAL( ctx.GetSymbol()->GetName(), wxS( "R" ) );
118
119 BOOST_CHECK( !ctx.IsModified() );
121 BOOST_CHECK( ctx.IsModified() );
122
123 // Marking the buffer's screen must not affect the owned context.
124 std::unique_ptr<SYMBOL_BUFFER> clean = makeBuffer( wxS( "C" ) );
125 SYMBOL_EDITOR_TAB_CONTEXT cleanCtx( wxS( "Device" ), wxS( "C" ), clean.get() );
126 clean->GetScreen()->SetContentModified();
127 BOOST_CHECK( !cleanCtx.IsModified() );
128
129 BOOST_CHECK_EQUAL( ctx.GetUnit(), 1 );
131
132 ctx.SetUnit( 2 );
133 ctx.SetBodyStyle( 2 );
134 BOOST_CHECK_EQUAL( ctx.GetUnit(), 2 );
136}
137
138
141BOOST_AUTO_TEST_CASE( InstanceTabContextIsTransientAndKeyedByUuid )
142{
143 KIID sourceUuid;
144 const wxString reference = wxS( "R5" );
145
146 LIB_SYMBOL* symbol = new LIB_SYMBOL( wxS( "R" ) );
147 SCH_SCREEN* screen = new SCH_SCREEN();
148
149 SYMBOL_EDITOR_TAB_CONTEXT ctx( symbol, screen, sourceUuid, reference );
150
151 BOOST_CHECK( ctx.IsTransient() );
152 BOOST_CHECK( ctx.IsFromSchematic() );
153 BOOST_CHECK_EQUAL( ctx.GetReference(), reference );
155 BOOST_CHECK_EQUAL( ctx.GetDisplayName(), reference );
156
159
160 // The instance key cannot collide with any library:name key.
161 BOOST_CHECK( ctx.GetTabKey() != SYMBOL_EDITOR_TAB_CONTEXT::MakeTabKey( wxS( "Device" ),
162 wxS( "R" ) ) );
163 BOOST_CHECK( ctx.GetTabKey().StartsWith( wxString( wxT( "\x01" ) ) ) );
164
165 BOOST_CHECK( ctx.GetSymbol() == symbol );
166 BOOST_CHECK( ctx.GetScreen() == screen );
167}
168
169
171BOOST_AUTO_TEST_CASE( SymbolTabContextSeedsDirtyFromBuffer )
172{
173 std::unique_ptr<SYMBOL_BUFFER> buf = makeBuffer( wxS( "R" ) );
174 buf->GetScreen()->SetContentModified();
175
176 SYMBOL_EDITOR_TAB_CONTEXT ctx( wxS( "Device" ), wxS( "R" ), buf.get() );
177 BOOST_CHECK( ctx.IsModified() );
178}
179
180
182BOOST_AUTO_TEST_CASE( OpenTabsJsonRoundTrip )
183{
185
186 source.m_OpenTabs.push_back( { wxS( "Device" ), wxS( "R" ), 1, 1 } );
187 source.m_OpenTabs.push_back( { wxS( "Connector" ), wxS( "Conn_01x02" ), 2, 1 } );
188 source.m_ActiveTabKey = wxS( "Connector:Conn_01x02" );
189
190 BOOST_REQUIRE( source.Store() );
191
192 std::string serialized = source.FormatAsString();
193 nlohmann::json reparsed = nlohmann::json::parse( serialized );
194
195 BOOST_REQUIRE( reparsed.contains( "open_tabs" ) );
196 BOOST_REQUIRE( reparsed["open_tabs"].is_array() );
197 BOOST_CHECK_EQUAL( reparsed["open_tabs"].size(), 2u );
198
200
201 // Seed a stale entry to catch a reader that fails to clear first.
202 sink.m_OpenTabs.push_back( { wxS( "STALE" ), wxS( "X" ), 1, 1 } );
203
204 JSON_SETTINGS_INTERNALS reparsedInternals;
205 static_cast<nlohmann::json&>( reparsedInternals ) = reparsed;
206 sink.Internals()->CloneFrom( reparsedInternals );
207 sink.Load();
208
209 BOOST_REQUIRE_EQUAL( sink.m_OpenTabs.size(), 2u );
210
211 BOOST_CHECK_EQUAL( sink.m_OpenTabs[0].lib, wxS( "Device" ) );
212 BOOST_CHECK_EQUAL( sink.m_OpenTabs[0].name, wxS( "R" ) );
213 BOOST_CHECK_EQUAL( sink.m_OpenTabs[0].unit, 1 );
214
215 BOOST_CHECK_EQUAL( sink.m_OpenTabs[1].lib, wxS( "Connector" ) );
216 BOOST_CHECK_EQUAL( sink.m_OpenTabs[1].name, wxS( "Conn_01x02" ) );
217 BOOST_CHECK_EQUAL( sink.m_OpenTabs[1].unit, 2 );
218
219 BOOST_CHECK_EQUAL( sink.m_ActiveTabKey, wxS( "Connector:Conn_01x02" ) );
220}
221
222
226BOOST_AUTO_TEST_CASE( FreeTransientUndoCommandsDeletesCopies )
227{
228 int dtorCount = 0;
229
232
233 pushTransientCommand( undo, new INSTRUMENTED_LIB_SYMBOL( wxS( "u1" ), &dtorCount ) );
234 pushTransientCommand( undo, new INSTRUMENTED_LIB_SYMBOL( wxS( "u2" ), &dtorCount ) );
235 pushTransientCommand( redo, new INSTRUMENTED_LIB_SYMBOL( wxS( "r1" ), &dtorCount ) );
236
237 BOOST_REQUIRE_EQUAL( undo.m_CommandsList.size(), 2u );
238 BOOST_REQUIRE_EQUAL( redo.m_CommandsList.size(), 1u );
239
240 freeTransientUndoCommands( undo, nullptr );
241 freeTransientUndoCommands( redo, nullptr );
242
243 BOOST_CHECK_EQUAL( dtorCount, 3 );
244 BOOST_CHECK_EQUAL( undo.m_CommandsList.size(), 0u );
245 BOOST_CHECK_EQUAL( redo.m_CommandsList.size(), 0u );
246}
247
248
252BOOST_AUTO_TEST_CASE( FreeTransientUndoCommandsSparesLiveSymbol )
253{
254 int dtorCount = 0;
255
256 auto liveSymbol = std::make_unique<INSTRUMENTED_LIB_SYMBOL>( wxS( "live" ), &dtorCount );
257
259
260 // A command referencing the live symbol without the transient flag, which the helper must skip.
261 PICKED_ITEMS_LIST* liveCmd = new PICKED_ITEMS_LIST();
262 liveCmd->PushItem( ITEM_PICKER( nullptr, liveSymbol.get(), UNDO_REDO::LIBEDIT ) );
263 undo.PushCommand( liveCmd );
264
265 pushTransientCommand( undo, new INSTRUMENTED_LIB_SYMBOL( wxS( "copy" ), &dtorCount ) );
266
267 freeTransientUndoCommands( undo, liveSymbol.get() );
268
269 BOOST_CHECK_EQUAL( dtorCount, 1 );
270 BOOST_CHECK_EQUAL( undo.m_CommandsList.size(), 0u );
271
272 liveSymbol.reset();
273 BOOST_CHECK_EQUAL( dtorCount, 2 );
274}
275
276
280BOOST_AUTO_TEST_CASE( ClearTabModifiedStateSyncsContextAndModel )
281{
282 std::unique_ptr<SYMBOL_BUFFER> buf = makeBuffer( wxS( "LMR16006" ) );
283
284 SYMBOL_EDITOR_TAB_CONTEXT ctx( wxS( "proj_lib_01" ), wxS( "LMR16006" ), buf.get() );
286
288 model.OpenDocument( ctx.GetTabKey(), /*asPreview*/ false );
289 model.MarkModified( ctx.GetTabKey(), true );
290
291 BOOST_REQUIRE( ctx.IsModified() );
292 BOOST_REQUIRE( model.Entries()[0].modified );
293
295
296 BOOST_CHECK( !ctx.IsModified() );
297 BOOST_CHECK( !model.Entries()[0].modified );
298}
299
300
305BOOST_AUTO_TEST_CASE( LibTreeAutoHiddenRestoreAcrossSchematicEdit )
306{
307 // First schematic edit with the tree visible schedules its restore.
308 BOOST_CHECK( libTreeAutoHiddenForSchematicEdit( false, false, true ) );
309
310 // First schematic edit with the tree already hidden (no pending restore) schedules nothing.
311 BOOST_CHECK( !libTreeAutoHiddenForSchematicEdit( false, false, false ) );
312
313 // Chained schematic edit finds the tree auto-hidden by the previous one; the pending restore
314 // must survive rather than drop.
315 BOOST_CHECK( libTreeAutoHiddenForSchematicEdit( true, true, false ) );
316
317 // A tree hidden without a pending restore (user toggled it off) stays unscheduled through a
318 // chained edit, so SaveSettings will not resurrect it.
319 BOOST_CHECK( !libTreeAutoHiddenForSchematicEdit( true, false, false ) );
320
321 // A visible tree always schedules a restore regardless of the prior pending state.
322 BOOST_CHECK( libTreeAutoHiddenForSchematicEdit( true, false, true ) );
323}
324
325
void SetContentModified(bool aModified=true)
Definition base_screen.h:55
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:152
GUI-free preview/dirty/close state machine.
Headless tests for the Symbol Editor tabbed-document plumbing.
INSTRUMENTED_LIB_SYMBOL(const wxString &aName, int *aDtorCounter)
void CloneFrom(const JSON_SETTINGS_INTERNALS &aOther)
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
const std::string FormatAsString()
JSON_SETTINGS_INTERNALS * Internals()
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
Definition kiid.h:44
wxString AsString() const
Definition kiid.cpp:242
Define a library symbol object.
Definition lib_symbol.h:80
wxString GetName() const override
Definition lib_symbol.h:142
LIB_SYMBOL(const wxString &aName, LIB_SYMBOL *aParent=nullptr, LEGACY_SYMBOL_LIB *aLibrary=nullptr)
A holder to handle information on schematic or board items.
void PushItem(const ITEM_PICKER &aItem)
Push aItem to the top of the list.
std::vector< OPEN_TAB > m_OpenTabs
One open symbol tab owning a working LIB_SYMBOL and screen lent to the frame while active.
wxString GetDisplayName() const override
Short label shown on the tab.
LIB_SYMBOL * GetSymbol() const
Observe the working symbol/screen.
const wxString & GetReference() const
static wxString MakeTabKey(const wxString &aLib, const wxString &aName)
De-duplication key for a library:symbol pair.
static wxString MakeInstanceTabKey(const KIID &aSchematicSymbolUUID)
De-duplication key for a placed schematic instance, in a namespace disjoint from library keys.
wxString GetTabKey() const override
Stable identity for persistence and de-duplication.
const KIID & GetSchematicSymbolUUID() const
bool IsModified() const override
True when the working screen carries unsaved edits.
bool IsTransient() const
True for an instance (schematic) tab, which is session-only and never persisted.
static void freeTransientUndoCommands(UNDO_REDO_CONTAINER &aList, const LIB_SYMBOL *aLiveSymbol)
Free every command in the list and the UR_TRANSIENT-flagged copies it owns, which the shared deleters...
static void clearTabModifiedState(SYMBOL_EDITOR_TAB_CONTEXT &aContext, EDITOR_TABS_MODEL &aModel)
Clear a tab's dirty state on both the context (read on repaint) and the strip model (read on close),...
static bool libTreeAutoHiddenForSchematicEdit(bool aWasFromSchematic, bool aRestorePending, bool aTreeShownNow)
Resolve whether the library tree's auto-hide for a schematic edit still needs restoring on save.
A holder to handle a list of undo (or redo) commands.
void PushCommand(PICKED_ITEMS_LIST *aCommand)
#define UR_TRANSIENT
indicates the item is owned by the undo/redo stack
static bool libTreeAutoHiddenForSchematicEdit(bool aWasFromSchematic, bool aRestorePending, bool aTreeShownNow)
Expose the library-tree visibility guard under test.
std::unique_ptr< SYMBOL_BUFFER > makeBuffer(const wxString &aName)
Build a buffer over a fresh symbol and screen for the given name.
void pushTransientCommand(UNDO_REDO_CONTAINER &aList, LIB_SYMBOL *aTransient)
Push a UR_TRANSIENT-flagged symbol copy as a single-item command.
static void freeTransientUndoCommands(UNDO_REDO_CONTAINER &aList, const LIB_SYMBOL *aLiveSymbol)
Expose the private static free helper under test.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_MODEL * model
BOOST_AUTO_TEST_CASE(SymbolTabContextOwnsWorkingCopy)
The context owns a working copy cloned from the buffer, seeds its dirty flag, and round-trips the per...
BOOST_CHECK_EQUAL(result, "25.4")