KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_sch_dynamic_properties.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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
24#include <schematic.h>
25#include <sch_field.h>
26#include <sch_screen.h>
27#include <sch_sheet.h>
28#include <sch_symbol.h>
30
31#include <algorithm>
32#include <optional>
33#include <vector>
34
35
36using namespace KICAD_DIFF;
37
38
39static const PROPERTY_DELTA* findDelta( const std::vector<PROPERTY_DELTA>& aDeltas, const wxString& aName )
40{
41 for( const PROPERTY_DELTA& d : aDeltas )
42 {
43 if( d.name == aName )
44 return &d;
45 }
46
47 return nullptr;
48}
49
50
52{
54 {
55 m_mgr.LoadProject( "" );
56 m_schematic = std::make_unique<SCHEMATIC>( &m_mgr.Prj() );
57 m_schematic->Reset();
58
59 m_screen = new SCH_SCREEN( m_schematic.get() );
60 m_sheet = new SCH_SHEET( m_schematic.get() );
61 m_sheet->SetScreen( m_screen );
62 m_schematic->AddTopLevelSheet( m_sheet );
63 }
64
65 SCH_SYMBOL* makeSymbol( const std::optional<wxString>& aFieldValue )
66 {
67 SCH_SYMBOL* symbol = new SCH_SYMBOL();
68 m_screen->Append( symbol );
69
70 if( aFieldValue.has_value() )
71 {
72 symbol->AddField( SCH_FIELD( symbol, FIELD_T::USER, wxS( "Manufacturer" ) ) );
73 symbol->GetField( wxS( "Manufacturer" ) )->SetText( *aFieldValue );
74 }
75
76 return symbol;
77 }
78
80 std::unique_ptr<SCHEMATIC> m_schematic;
83};
84
85
86BOOST_FIXTURE_TEST_SUITE( SchDynamicProperties, SCH_DYNAMIC_FIXTURE )
87
88
89BOOST_AUTO_TEST_CASE( SymbolDynamicPropertyOrder )
90{
91 SCH_SYMBOL symbol;
92
93 symbol.AddField( SCH_FIELD( &symbol, FIELD_T::USER, wxS( "Zulu" ) ) );
94 symbol.AddField( SCH_FIELD( &symbol, FIELD_T::USER, wxS( "alpha" ) ) );
95 symbol.AddField( SCH_FIELD( &symbol, FIELD_T::USER, wxS( "Mike" ) ) );
96
97 SCH_FIELD privateField( &symbol, FIELD_T::USER, wxS( "Secret" ) );
98 privateField.SetPrivate( true );
99 symbol.AddField( privateField );
100
101 std::vector<PROPERTY_BASE*> dynamicProps = symbol.GetDynamicProperties();
102
103 std::vector<wxString> names;
104 names.reserve( dynamicProps.size() );
105
106 for( PROPERTY_BASE* prop : dynamicProps )
107 names.push_back( prop->Name() );
108
109 BOOST_CHECK( std::ranges::none_of( names,
110 []( const wxString& n ) { return n == wxS( "Secret" ); } ) );
111
112 // Mandatory non-private fields come first (in GetFields() order), then the
113 // user fields sorted case-insensitively by name.
114 bool seenUser = false;
115 wxString lastUser;
116 std::vector<wxString> userNames;
117
118 for( const wxString& name : names )
119 {
120 const SCH_FIELD* field = symbol.GetField( name );
121
122 if( field && field->IsMandatory() )
123 {
124 BOOST_CHECK_MESSAGE( !seenUser, "Mandatory field '" + name + "' appears after a user field" );
125 }
126 else
127 {
128 if( field )
129 BOOST_CHECK( !field->IsPrivate() );
130
131 if( seenUser )
132 BOOST_CHECK_MESSAGE( lastUser.CmpNoCase( name ) <= 0,
133 "User fields not sorted: " + lastUser + " before " + name );
134
135 lastUser = name;
136 userNames.push_back( name );
137 seenUser = true;
138 }
139 }
140
141 const std::vector<wxString> expectedUser = { wxS( "alpha" ), wxS( "Mike" ), wxS( "Zulu" ) };
142 BOOST_CHECK( userNames == expectedUser );
143}
144
145
146BOOST_AUTO_TEST_CASE( SheetDynamicPropertyOrder )
147{
148 SCH_SHEET sheet;
149
150 sheet.AddField( SCH_FIELD( &sheet, FIELD_T::SHEET_USER, wxS( "delta" ) ) );
151 sheet.AddField( SCH_FIELD( &sheet, FIELD_T::SHEET_USER, wxS( "Alpha" ) ) );
152 sheet.AddField( SCH_FIELD( &sheet, FIELD_T::SHEET_USER, wxS( "charlie" ) ) );
153
154 std::vector<PROPERTY_BASE*> dynamicProps = sheet.GetDynamicProperties();
155
156 std::vector<wxString> names;
157 names.reserve( dynamicProps.size() );
158
159 for( PROPERTY_BASE* prop : dynamicProps )
160 names.push_back( prop->Name() );
161
162 bool seenUser = false;
163 wxString lastUser;
164 std::vector<wxString> userNames;
165
166 for( const wxString& name : names )
167 {
168 const SCH_FIELD* field = sheet.GetField( name );
169
170 if( field && field->IsMandatory() )
171 {
172 BOOST_CHECK_MESSAGE( !seenUser,
173 "Mandatory field '" + name + "' appears after a user field" );
174 }
175 else
176 {
177 if( seenUser )
178 {
179 BOOST_CHECK_MESSAGE( lastUser.CmpNoCase( name ) <= 0,
180 "User fields not sorted: " + lastUser + " before " + name );
181 }
182
183 lastUser = name;
184 userNames.push_back( name );
185 seenUser = true;
186 }
187 }
188
189 const std::vector<wxString> expectedUser = { wxS( "Alpha" ), wxS( "charlie" ), wxS( "delta" ) };
190 BOOST_CHECK( userNames == expectedUser );
191}
192
193
194BOOST_AUTO_TEST_CASE( DynamicSkipsStaticFields )
195{
196 SCH_SYMBOL symbol;
197
199 const std::vector<PROPERTY_BASE*>& staticProps = pm.GetProperties( TYPE_HASH( SCH_SYMBOL ) );
200
201 for( PROPERTY_BASE* dynamic : symbol.GetDynamicProperties() )
202 {
203 std::vector<wxString> staticNames;
204
205 for( PROPERTY_BASE* staticProp : staticProps )
206 staticNames.push_back( staticProp->Name() );
207
208 BOOST_CHECK_MESSAGE( std::ranges::find( staticNames, dynamic->Name() ) == staticNames.end(),
209 "Dynamic property '" + dynamic->Name() + "' duplicates a statically-registered property" );
210 }
211}
212
213
214BOOST_AUTO_TEST_CASE( FieldGetterReturnsNullForAbsentField )
215{
216 SCH_SYMBOL* symWith = makeSymbol( wxString( wxS( "ACME" ) ) );
217 SCH_SYMBOL* symWithout = makeSymbol( std::nullopt );
218
219 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( symWithout, symWith );
220 const PROPERTY_DELTA* d = findDelta( deltas, wxS( "Manufacturer" ) );
221
222 BOOST_REQUIRE( d );
223 BOOST_CHECK( d->before.GetType() == DIFF_VALUE::T::NONE );
224 BOOST_CHECK( d->after.GetType() == DIFF_VALUE::T::STRING );
225}
226
227
228BOOST_AUTO_TEST_CASE( FieldGetterReturnsEmptyStringForEmptyField )
229{
230 SCH_SYMBOL* symA = makeSymbol( wxString( wxS( "" ) ) ); // Manufacturer == ""
231 SCH_SYMBOL* symB = makeSymbol( wxString( wxS( "ACME" ) ) ); // Manufacturer == "ACME"
232
233 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( symA, symB );
234 const PROPERTY_DELTA* d = findDelta( deltas, wxS( "Manufacturer" ) );
235
236 BOOST_REQUIRE( d );
237
238 BOOST_CHECK( d->before.GetType() == DIFF_VALUE::T::STRING );
239 BOOST_CHECK( d->after.GetType() == DIFF_VALUE::T::STRING );
240 BOOST_CHECK( d->before.GetType() != DIFF_VALUE::T::NONE );
241}
242
243
244BOOST_AUTO_TEST_CASE( BothSidesAbsentFieldProducesNoDelta )
245{
246 SCH_SYMBOL* symA = makeSymbol( std::nullopt );
247 SCH_SYMBOL* symB = makeSymbol( std::nullopt );
248
249 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( symA, symB );
250 BOOST_CHECK( !findDelta( deltas, wxS( "Manufacturer" ) ) );
251}
252
253
254BOOST_AUTO_TEST_CASE( BothSidesEmptyFieldProducesNoDelta )
255{
256 SCH_SYMBOL* symA = makeSymbol( wxString( wxS( "" ) ) );
257 SCH_SYMBOL* symB = makeSymbol( wxString( wxS( "" ) ) );
258
259 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( symA, symB );
260 BOOST_CHECK( !findDelta( deltas, wxS( "Manufacturer" ) ) );
261}
262
263
264BOOST_AUTO_TEST_CASE( DynamicFieldSetterCreatesField )
265{
266 SCH_SYMBOL symbol;
267
268 symbol.AddField( SCH_FIELD( &symbol, FIELD_T::USER, wxS( "TempField" ) ) );
269
270 PROPERTY_BASE* prop = nullptr;
271
272 for( PROPERTY_BASE* p : symbol.GetDynamicProperties() )
273 {
274 if( p->Name() == wxS( "TempField" ) )
275 prop = p;
276 }
277
278 BOOST_REQUIRE( prop );
279
280 symbol.RemoveField( wxS( "TempField" ) );
281 BOOST_REQUIRE( !symbol.GetField( wxS( "TempField" ) ) );
282
283 // Re-applying the cached property must recreate the removed field.
284 wxAny value( wxString( wxS( "newval" ) ) );
285 BOOST_CHECK( symbol.Set( prop, value ) );
286
287 SCH_FIELD* recreated = symbol.GetField( wxS( "TempField" ) );
288 BOOST_REQUIRE( recreated );
289 BOOST_CHECK_EQUAL( recreated->GetText(), wxS( "newval" ) );
290}
291
292
293BOOST_AUTO_TEST_CASE( AddedFieldSurfacesAsDelta )
294{
295 SCH_SYMBOL* symA = makeSymbol( std::nullopt ); // no Manufacturer
296 SCH_SYMBOL* symB = makeSymbol( wxString( wxS( "ACME" ) ) );
297
298 // Field added between revisions: present only on the "after" side.
299 std::vector<PROPERTY_DELTA> deltas = DiffItemProperties( symA, symB );
300 const PROPERTY_DELTA* d = findDelta( deltas, wxS( "Manufacturer" ) );
301
302 BOOST_REQUIRE( d );
303 BOOST_CHECK( d->before.GetType() == DIFF_VALUE::T::NONE );
304 BOOST_CHECK( d->after.GetType() == DIFF_VALUE::T::STRING );
305
306 // Reverse direction (field removed): present on the "before" side.
307 std::vector<PROPERTY_DELTA> reverse = DiffItemProperties( symB, symA );
308 const PROPERTY_DELTA* rd = findDelta( reverse, wxS( "Manufacturer" ) );
309
310 BOOST_REQUIRE( rd );
311 BOOST_CHECK( rd->before.GetType() == DIFF_VALUE::T::STRING );
312 BOOST_CHECK( rd->after.GetType() == DIFF_VALUE::T::NONE );
313}
314
315
316BOOST_AUTO_TEST_CASE( CustomPropertiesExposedAsDynamic )
317{
318 SCH_SYMBOL symbol;
319
320 symbol.SetCustomProperty( wxS( "MPN" ), wxS( "12345" ) );
321
322 PROPERTY_BASE* mpn = nullptr;
323
324 for( PROPERTY_BASE* p : symbol.GetDynamicProperties() )
325 {
326 if( p->Name() == wxS( "MPN" ) )
327 mpn = p;
328 }
329
330 BOOST_REQUIRE( mpn );
331
332 wxAny v = symbol.Get( mpn );
333 wxString text;
334
335 BOOST_REQUIRE( v.GetAs( &text ) );
336 BOOST_CHECK_EQUAL( text, wxS( "12345" ) );
337
338 wxAny newVal( wxString( wxS( "99999" ) ) );
339 BOOST_CHECK( symbol.Set( mpn, newVal ) );
340 BOOST_CHECK_EQUAL( symbol.GetCustomProperties().at( wxS( "MPN" ) ), wxS( "99999" ) );
341}
342
343
const char * name
void SetCustomProperty(const wxString &aKey, const wxString &aValue)
Definition eda_item.h:255
const std::map< wxString, wxString > & GetCustomProperties() const
Custom user-defined string key/value properties attached to this item.
Definition eda_item.h:242
wxAny Get(PROPERTY_BASE *aProperty) const
bool Set(PROPERTY_BASE *aProperty, wxAny &aValue, bool aNotify=true)
Provide class metadata.Helper macro to map type hashes to names.
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
static PROPERTY_MANAGER & Instance()
bool IsMandatory() const
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
void SetPrivate(bool aPrivate)
Definition sch_item.h:252
bool IsPrivate() const
Definition sch_item.h:253
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this sheet.
std::vector< PROPERTY_BASE * > GetDynamicProperties() const override
Return dynamically-computed properties specific to this object instance (e.g.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a @aField to the list of fields.
Schematic symbol object.
Definition sch_symbol.h:75
void RemoveField(const wxString &aFieldName)
Remove a user field from the symbol.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
std::vector< PROPERTY_BASE * > GetDynamicProperties() const override
Return dynamically-computed properties specific to this object instance (e.g.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
std::vector< PROPERTY_DELTA > DiffItemProperties(const INSPECTABLE *aBefore, const INSPECTABLE *aAfter)
Enumerate the property deltas between two items of the same dynamic type.
#define TYPE_HASH(x)
Definition property.h:74
Single (name, before, after) triple for one mutated property on an item.
SCH_SYMBOL * makeSymbol(const std::optional< wxString > &aFieldValue)
std::unique_ptr< SCHEMATIC > m_schematic
@ USER
The field ID hasn't been set yet; field is invalid.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(SymbolDynamicPropertyOrder)
static const PROPERTY_DELTA * findDelta(const std::vector< PROPERTY_DELTA > &aDeltas, const wxString &aName)
BOOST_CHECK_EQUAL(result, "25.4")