KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_issue25112_shared_sheet_field_scope.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
20// Regression test for https://gitlab.com/kicad/code/kicad/-/issues/25112
21
23
24#include <memory>
25#include <set>
26
27#include <eeschema_helpers.h>
29#include <locale_io.h>
30#include <sch_commit.h>
31#include <sch_field.h>
32#include <sch_reference_list.h>
33#include <sch_sheet_path.h>
34#include <sch_symbol.h>
35#include <schematic.h>
36#include <template_fieldnames.h>
37#include <tool/tool_manager.h>
38
39
40// The reproduction case instantiates level1.kicad_sch twice from the root and level2.kicad_sch
41// twice from level1, so most symbols live in a single SCH_SYMBOL reachable through several
42// sheet paths. Editing a field with the fields table scoped to one sheet only updated the data
43// store entries for the visible sheet paths; ApplyData walks every path, so the paths left out
44// of scope wrote their stale value back over the edit and the footprint appeared to revert as
45// soon as the dialog was applied.
47{
49 {
50 wxString schPath = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() )
51 + wxS( "issue25112/issue25112.kicad_sch" );
52
53 m_schematic.reset( EESCHEMA_HELPERS::LoadSchematic( schPath, true, false ) );
54 BOOST_REQUIRE( m_schematic != nullptr );
55
56 m_schematic->Hierarchy().GetSymbols( m_refs, SYMBOL_FILTER_NON_POWER );
57 }
58
65 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> MakeScopedModel( const wxString& aVariantName,
66 const wxString& aFieldName,
67 bool aAddedByUser = false )
68 {
69 auto model = std::make_unique<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL>( m_refs );
70
71 model->SetCurrentVariant( aVariantName );
72 model->AddColumn( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ), wxS( "Reference" ), false );
73 model->AddColumn( aFieldName, aFieldName, aAddedByUser );
74
75 int referenceCol = model->GetFieldNameCol( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ) );
76 BOOST_REQUIRE( referenceCol >= 0 );
77 model->SetShowColumn( referenceCol, true );
78
79 m_col = model->GetFieldNameCol( aFieldName );
80 BOOST_REQUIRE( m_col >= 0 );
81 model->SetShowColumn( m_col, true );
82
83 const SCH_REFERENCE_LIST& modelRefs = model->GetReferenceList();
84 bool found = false;
85
86 for( size_t ii = 0; ii < modelRefs.GetCount() && !found; ++ii )
87 {
88 for( size_t jj = ii + 1; jj < modelRefs.GetCount(); ++jj )
89 {
90 if( modelRefs[jj].GetSymbol() == modelRefs[ii].GetSymbol() )
91 {
92 m_symbol = modelRefs[ii].GetSymbol();
93 m_scopePath = modelRefs[ii].GetSheetPath();
94 m_siblingPath = modelRefs[jj].GetSheetPath();
95 found = true;
96 break;
97 }
98 }
99 }
100
101 BOOST_REQUIRE( found );
102
103 model->SetPath( m_scopePath );
104 model->SetScope( SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET );
105 model->RebuildRows();
106
107 m_row = -1;
108
109 for( int ii = 0; ii < model->GetNumberRows() && m_row < 0; ++ii )
110 {
111 for( const SCH_REFERENCE& ref : model->GetRowReferences( ii ) )
112 {
113 if( ref.GetSymbol() == m_symbol )
114 {
115 m_row = ii;
116 break;
117 }
118 }
119 }
120
121 BOOST_REQUIRE( m_row >= 0 );
122
123 // Sanity: the sheet scope hides the symbol's other paths, which is what left their data
124 // store entries stale.
125 BOOST_REQUIRE_EQUAL( model->GetRowReferences( m_row ).size(), 1u );
126
127 return model;
128 }
129
130 void Apply( SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL& aModel, const wxString& aVariantName )
131 {
132 TOOL_MANAGER toolMgr;
133 SCH_COMMIT commit( &toolMgr );
134 TEMPLATES templates;
135
136 aModel.ApplyData( commit, templates, aVariantName );
137 }
138
140 std::unique_ptr<SCHEMATIC> m_schematic;
145 int m_col = -1;
146 int m_row = -1;
147};
148
149
150BOOST_FIXTURE_TEST_CASE( SheetScopedFieldEditSurvivesApply, ISSUE25112_FIXTURE )
151{
152 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
153 MakeScopedModel( wxEmptyString, GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) );
154
155 const wxString newFootprint = wxS( "Resistor_SMD:R_0603_1608Metric" );
156
157 model->SetValue( m_row, m_col, newFootprint );
158 Apply( *model, wxEmptyString );
159
160 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText(), newFootprint );
161}
162
163
164BOOST_FIXTURE_TEST_CASE( SheetScopedFieldClearCanBeReverted, ISSUE25112_FIXTURE )
165{
166 const wxString fieldName = wxS( "MPN" );
167 const wxString fieldValue = wxS( "ABC123" );
168
169 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model = MakeScopedModel( wxEmptyString, fieldName );
170
171 SCH_FIELD field( m_symbol, FIELD_T::USER, fieldName );
172 field.SetText( fieldValue );
173 m_symbol->AddField( field );
174 model->UpdateReferences( m_refs );
175
176 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
177
178 model->ClearCell( m_row, m_col );
179 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
180
181 model->RevertRow( m_row );
182 BOOST_CHECK( !model->IsCellClear( m_row, m_col ) );
183 BOOST_CHECK( !model->IsEdited() );
184
185 Apply( *model, wxEmptyString );
186
187 const SCH_FIELD* appliedField = m_symbol->GetField( fieldName );
188 BOOST_REQUIRE( appliedField );
189 BOOST_CHECK_EQUAL( appliedField->GetText(), fieldValue );
190}
191
192
193BOOST_FIXTURE_TEST_CASE( GroupedEditStateChecksEveryItem, ISSUE25112_FIXTURE )
194{
195 const wxString fieldName = wxS( "GroupedState" );
196 const wxString fieldValue = wxS( "Original" );
197 std::set<SCH_SYMBOL*> symbols;
198
199 for( const SCH_REFERENCE& ref : m_refs )
200 {
201 SCH_SYMBOL* symbol = ref.GetSymbol();
202
203 if( !symbols.insert( symbol ).second )
204 continue;
205
206 SCH_FIELD field( symbol, FIELD_T::USER, fieldName );
207 field.SetText( fieldValue );
208 symbol->AddField( field );
209 }
210
212 model.AddColumn( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ), wxS( "Reference" ), false );
213 model.AddColumn( fieldName, fieldName, false );
214
215 int referenceCol = model.GetFieldNameCol( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ) );
216 int fieldCol = model.GetFieldNameCol( fieldName );
217 BOOST_REQUIRE( referenceCol >= 0 );
218 BOOST_REQUIRE( fieldCol >= 0 );
219
220 model.SetShowColumn( referenceCol, true );
221 model.SetShowColumn( fieldCol, true );
222 model.SetGroupingEnabled( true );
223 model.SetGroupColumn( fieldCol, true );
224 model.RebuildRows();
225
226 int groupedRow = -1;
227 SCH_SYMBOL* symbolToModify = nullptr;
228
229 for( int row = 0; row < model.GetNumberRows() && groupedRow < 0; ++row )
230 {
231 const std::vector<SCH_REFERENCE> refs = model.GetRowReferences( row );
232
233 for( size_t ii = 1; ii < refs.size(); ++ii )
234 {
235 if( refs[ii].GetSymbol() != refs[0].GetSymbol() )
236 {
237 groupedRow = row;
238 symbolToModify = refs[ii].GetSymbol();
239 break;
240 }
241 }
242 }
243
244 BOOST_REQUIRE( groupedRow >= 0 );
245 BOOST_REQUIRE( symbolToModify );
246 BOOST_REQUIRE( !model.IsRowEdited( groupedRow ) );
247
248 symbolToModify->GetField( fieldName )->SetText( wxS( "Modified" ) );
249
250 BOOST_CHECK( !model.IsCellEdited( groupedRow, referenceCol ) );
251 BOOST_CHECK( model.IsCellEdited( groupedRow, fieldCol ) );
252 BOOST_CHECK( model.IsRowEdited( groupedRow ) );
253}
254
255
256// Variant field values are stored per symbol instance, so an edit made against one sheet path
257// must not be stamped onto the paths that share the symbol.
258BOOST_FIXTURE_TEST_CASE( SheetScopedVariantEditStaysOnItsPath, ISSUE25112_FIXTURE )
259{
260 const wxString variant = wxS( "Assembly" );
261
262 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
263 MakeScopedModel( variant, GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) );
264
265 const wxString baseFootprint = m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText();
266 const wxString newFootprint = wxS( "Resistor_SMD:R_0603_1608Metric" );
267
268 BOOST_REQUIRE( baseFootprint != newFootprint );
269
270 model->SetValue( m_row, m_col, newFootprint );
271 Apply( *model, variant );
272
273 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText( &m_scopePath, variant ), newFootprint );
274 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText( &m_siblingPath, variant ), baseFootprint );
275 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText(), baseFootprint );
276}
277
278
279// The board exclusion has no variant form in SCH_REFERENCE, so it lands on the symbol even with
280// a variant selected and still has to reach the sheet paths the scope hides.
281BOOST_FIXTURE_TEST_CASE( SheetScopedBoardExclusionSurvivesApply, ISSUE25112_FIXTURE )
282{
283 const wxString variant = wxS( "Assembly" );
284
285 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
286 MakeScopedModel( variant, wxS( "${EXCLUDE_FROM_BOARD}" ) );
287
288 BOOST_REQUIRE( !m_symbol->GetExcludedFromBoard() );
289
290 model->SetValue( m_row, m_col, wxS( "1" ) );
291 Apply( *model, variant );
292
293 BOOST_CHECK( m_symbol->GetExcludedFromBoard() );
294}
295
296
297BOOST_FIXTURE_TEST_CASE( UntouchedMissingPresetFieldRemainsAbsent, ISSUE25112_FIXTURE )
298{
299 const wxString fieldName = wxS( "UninstantiatedPresetField" );
300
301 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
302 MakeScopedModel( wxEmptyString, fieldName );
303
304 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
305
306 Apply( *model, wxEmptyString );
307
308 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
309}
310
311
312BOOST_FIXTURE_TEST_CASE( ExplicitEmptyValueCreatesField, ISSUE25112_FIXTURE )
313{
314 const wxString fieldName = wxS( "ExplicitlyEmptyField" );
315
316 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
317 MakeScopedModel( wxEmptyString, fieldName );
318
319 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
320
321 model->SetValue( m_row, m_col, wxEmptyString );
322
323 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
324 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
325
326 Apply( *model, wxEmptyString );
327
328 const SCH_FIELD* field = m_symbol->GetField( fieldName );
329 BOOST_REQUIRE( field );
330 BOOST_CHECK( field->GetText().IsEmpty() );
331}
332
333
334BOOST_FIXTURE_TEST_CASE( ExistingEmptyFieldCanBeCleared, ISSUE25112_FIXTURE )
335{
336 const wxString fieldName = wxS( "ExistingEmptyField" );
337
338 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
339 MakeScopedModel( wxEmptyString, fieldName );
340
341 m_symbol->AddField( SCH_FIELD( m_symbol, FIELD_T::USER, fieldName ) );
342 model->UpdateReferences( m_refs );
343
344 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
345 BOOST_REQUIRE( !model->IsCellEdited( m_row, m_col ) );
346
347 model->ClearCell( m_row, m_col );
348
349 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
350 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
351
352 Apply( *model, wxEmptyString );
353
354 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
355}
356
357
358BOOST_FIXTURE_TEST_CASE( UserAddedColumnCreatesEmptyField, ISSUE25112_FIXTURE )
359{
360 const wxString fieldName = wxS( "UserAddedEmptyField" );
361
362 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
363 MakeScopedModel( wxEmptyString, fieldName, true );
364
365 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
366 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
367
368 Apply( *model, wxEmptyString );
369
370 const SCH_FIELD* field = m_symbol->GetField( fieldName );
371 BOOST_REQUIRE( field );
372 BOOST_CHECK( field->GetText().IsEmpty() );
373}
374
375
376BOOST_FIXTURE_TEST_CASE( RevertingVariantFieldCreationRestoresAbsence, ISSUE25112_FIXTURE )
377{
378 const wxString fieldName = wxS( "RevertedVariantField" );
379
380 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
381 MakeScopedModel( wxS( "Assembly" ), fieldName );
382
383 model->SetValue( m_row, m_col, wxEmptyString );
384 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
385
386 model->RevertRow( m_row );
387
388 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
389 BOOST_REQUIRE( !model->IsEdited() );
390
391 Apply( *model, wxS( "Assembly" ) );
392
393 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
394}
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:138
void SetText(const wxString &aText) override
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
A helper to define a symbol's reference designator in a schematic.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Schematic symbol object.
Definition sch_symbol.h:75
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
void ApplyData(SCH_COMMIT &aCommit, TEMPLATES &aTemplateFieldnames, const wxString &aVariantName)
Master controller class:
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_NON_POWER
std::unique_ptr< SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL > MakeScopedModel(const wxString &aVariantName, const wxString &aFieldName, bool aAddedByUser=false)
Build a model scoped to the first sheet path of a symbol that a later path also reaches.
void Apply(SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL &aModel, const wxString &aVariantName)
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ USER
The field ID hasn't been set yet; field is invalid.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ UNTRANSLATED
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_FIXTURE_TEST_CASE(SheetScopedFieldEditSurvivesApply, ISSUE25112_FIXTURE)
KIBIS_MODEL * model
BOOST_CHECK_EQUAL(result, "25.4")