KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_text_var_dependency.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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#define BOOST_TEST_NO_MAIN
21#include <boost/test/unit_test.hpp>
22
23#include <algorithm>
24#include <common.h>
25#include <eda_item.h>
26#include <eda_text.h>
27#include <base_units.h>
28#include <text_var_dependency.h>
29
30
31BOOST_AUTO_TEST_SUITE( TextVarDependency )
32
33
34BOOST_AUTO_TEST_CASE( KeyFromToken_Plain )
35{
37 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::LOCAL );
38 BOOST_CHECK( k.primary == wxT( "VALUE" ) );
39 BOOST_CHECK( k.secondary == wxT( "" ) );
40}
41
42
43BOOST_AUTO_TEST_CASE( KeyFromToken_CrossRef )
44{
45 TEXT_VAR_REF_KEY k = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) );
46 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::CROSS_REF );
47 BOOST_CHECK( k.primary == wxT( "U1" ) );
48 BOOST_CHECK( k.secondary == wxT( "VALUE" ) );
49}
50
51
52BOOST_AUTO_TEST_CASE( KeyFromToken_LeadingColon )
53{
54 // Leading colon is not a cross-reference; fall back to PLAIN with the full token.
55 TEXT_VAR_REF_KEY k = TEXT_VAR_REF_KEY::FromToken( wxT( ":VALUE" ) );
56 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::LOCAL );
57 BOOST_CHECK( k.primary == wxT( ":VALUE" ) );
58}
59
60
61BOOST_AUTO_TEST_CASE( KeyFromToken_TrailingColon )
62{
64 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::LOCAL );
65 BOOST_CHECK( k.primary == wxT( "U1:" ) );
66}
67
68
69BOOST_AUTO_TEST_CASE( KeyFromToken_TitleBlockField )
70{
71 TEXT_VAR_REF_KEY k = TEXT_VAR_REF_KEY::FromToken( wxT( "PROJECTNAME" ) );
72 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK );
73 BOOST_CHECK( k.primary == wxT( "PROJECTNAME" ) );
74
75 TEXT_VAR_REF_KEY rev = TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) );
76 BOOST_CHECK( rev.kind == TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK );
77
78 TEXT_VAR_REF_KEY c9 = TEXT_VAR_REF_KEY::FromToken( wxT( "COMMENT9" ) );
79 BOOST_CHECK( c9.kind == TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK );
80}
81
82
83BOOST_AUTO_TEST_CASE( KeyFromToken_SpecialContextualToken )
84{
85 TEXT_VAR_REF_KEY k = TEXT_VAR_REF_KEY::FromToken( wxT( "SHEETNAME" ) );
86 BOOST_CHECK( k.kind == TEXT_VAR_REF_KEY::KIND::SPECIAL );
87 BOOST_CHECK( k.primary == wxT( "SHEETNAME" ) );
88}
89
90
91BOOST_AUTO_TEST_CASE( KeyFromToken_SPICEOperatingPoint )
92{
94 BOOST_CHECK( bare.kind == TEXT_VAR_REF_KEY::KIND::OP );
95
96 TEXT_VAR_REF_KEY port = TEXT_VAR_REF_KEY::FromToken( wxT( "OP:1" ) );
97 BOOST_CHECK( port.kind == TEXT_VAR_REF_KEY::KIND::OP );
98 BOOST_CHECK( port.primary == wxT( "OP" ) );
99 BOOST_CHECK( port.secondary == wxT( "1" ) );
100
101 // IsTrackable() gates out OP keys so the tracker never registers them.
102 BOOST_CHECK( !bare.IsTrackable() );
103 BOOST_CHECK( !port.IsTrackable() );
104 BOOST_CHECK( TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) ).IsTrackable() );
105}
106
107
109{
110 TEXT_VAR_REF_KEY a = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) );
111 TEXT_VAR_REF_KEY b = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) );
112 TEXT_VAR_REF_KEY c = TEXT_VAR_REF_KEY::FromToken( wxT( "U2:VALUE" ) );
113 TEXT_VAR_REF_KEY d = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:MPN" ) );
114
115 BOOST_CHECK( a == b );
116 BOOST_CHECK( !( a == c ) );
117 BOOST_CHECK( !( a == d ) );
118}
119
120
121BOOST_AUTO_TEST_CASE( KeyHash_Distinguishes )
122{
124
125 TEXT_VAR_REF_KEY plain;
127 plain.primary = wxT( "U1:VALUE" );
128
129 TEXT_VAR_REF_KEY cross;
131 cross.primary = wxT( "U1" );
132 cross.secondary = wxT( "VALUE" );
133
134 // A plain token whose primary happens to equal "U1:VALUE" must not collide
135 // with the cross-ref key {U1, VALUE}. Different KIND must participate in the hash.
136 BOOST_CHECK_NE( hasher( plain ), hasher( cross ) );
137}
138
139
140BOOST_AUTO_TEST_CASE( Extract_NoReferences )
141{
142 auto refs = ExtractTextVarReferences( wxT( "plain text, nothing to see" ) );
143 BOOST_CHECK_EQUAL( refs.size(), 0u );
144}
145
146
147BOOST_AUTO_TEST_CASE( Extract_SinglePlain )
148{
149 auto refs = ExtractTextVarReferences( wxT( "${VALUE}" ) );
150 BOOST_REQUIRE_EQUAL( refs.size(), 1u );
151 BOOST_CHECK( refs[0].kind == TEXT_VAR_REF_KEY::KIND::LOCAL );
152 BOOST_CHECK( refs[0].primary == wxT( "VALUE" ) );
153}
154
155
156BOOST_AUTO_TEST_CASE( Extract_SingleCrossRef )
157{
158 auto refs = ExtractTextVarReferences( wxT( "${U1:VALUE}" ) );
159 BOOST_REQUIRE_EQUAL( refs.size(), 1u );
160 BOOST_CHECK( refs[0].kind == TEXT_VAR_REF_KEY::KIND::CROSS_REF );
161 BOOST_CHECK( refs[0].primary == wxT( "U1" ) );
162 BOOST_CHECK( refs[0].secondary == wxT( "VALUE" ) );
163}
164
165
166BOOST_AUTO_TEST_CASE( Extract_MultipleReferences )
167{
168 auto refs = ExtractTextVarReferences( wxT( "R=${R12:VALUE}, C=${C3:VALUE}, sheet=${SHEETNAME}" ) );
169 BOOST_REQUIRE_EQUAL( refs.size(), 3u );
170
171 auto hasKey = [&]( TEXT_VAR_REF_KEY::KIND k, const wxString& p, const wxString& s )
172 {
173 return std::any_of( refs.begin(), refs.end(),
174 [&]( const TEXT_VAR_REF_KEY& ref )
175 { return ref.kind == k && ref.primary == p && ref.secondary == s; } );
176 };
177
178 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::CROSS_REF, wxT( "R12" ), wxT( "VALUE" ) ) );
179 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::CROSS_REF, wxT( "C3" ), wxT( "VALUE" ) ) );
180 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::SPECIAL, wxT( "SHEETNAME" ), wxT( "" ) ) );
181}
182
183
184BOOST_AUTO_TEST_CASE( Extract_EscapedReferenceIgnored )
185{
186 // `\${VALUE}` must not generate a dependency edge — the literal text is intended.
187 auto refs = ExtractTextVarReferences( wxT( "literal \\${VALUE} here" ) );
188 BOOST_CHECK_EQUAL( refs.size(), 0u );
189}
190
191
192BOOST_AUTO_TEST_CASE( Extract_MalformedTokenStillCapturesInnerRef )
193{
194 // `${FOO${BAR}` — outer closing brace missing (premature EOF). The
195 // partial body still contains a fully-formed inner ${BAR} that must be
196 // captured as a dependency; dropping it would silently lose edges on
197 // user-in-progress edits.
198 auto refs = ExtractTextVarReferences( wxT( "${FOO${BAR}" ) );
199
200 bool hasBar = std::any_of( refs.begin(), refs.end(),
201 []( const TEXT_VAR_REF_KEY& k )
202 { return k.primary == wxT( "BAR" ); } );
203
204 BOOST_CHECK( hasBar );
205}
206
207
208BOOST_AUTO_TEST_CASE( Extract_NestedReferences )
209{
210 // ${FOO_${BAR}} — both the outer token (literal form) and the inner BAR
211 // must be captured as dependency edges.
212 auto refs = ExtractTextVarReferences( wxT( "${FOO_${BAR}}" ) );
213
214 bool hasBar = std::any_of( refs.begin(), refs.end(),
215 []( const TEXT_VAR_REF_KEY& k )
216 { return k.primary == wxT( "BAR" ); } );
217 bool hasOuter = std::any_of( refs.begin(), refs.end(),
218 []( const TEXT_VAR_REF_KEY& k )
219 { return k.primary.Contains( wxT( "FOO_" ) ); } );
220
221 BOOST_CHECK( hasBar );
222 BOOST_CHECK( hasOuter );
223}
224
225
226BOOST_AUTO_TEST_CASE( Extract_ERC_WARNING_IsCaptured )
227{
228 // Codex finding 3: ExpandTextVars' resolver short-circuits ERC_WARNING/
229 // DRC_WARNING tokens when not in an ERC/DRC context, so the old collector
230 // approach silently dropped them from the dependency graph. The dedicated
231 // lexer captures them regardless.
232 auto refs = ExtractTextVarReferences( wxT( "${ERC_WARNING something}" ) );
233
234 BOOST_REQUIRE_EQUAL( refs.size(), 1u );
235 BOOST_CHECK( refs[0].primary == wxT( "ERC_WARNING something" ) );
236}
237
238
239BOOST_AUTO_TEST_CASE( Extract_NestedDynamicCrossRef )
240{
241 // ${U1:NET_NAME(@{${ROW}-2})} — the outer token captures the full body
242 // literally, ROW is captured as a nested dependency via the @{} math
243 // wrapper.
244 auto refs = ExtractTextVarReferences( wxT( "${U1:NET_NAME(@{${ROW}-2})}" ) );
245
246 bool hasRow = std::any_of( refs.begin(), refs.end(),
247 []( const TEXT_VAR_REF_KEY& k )
248 { return k.primary == wxT( "ROW" ); } );
249 bool hasOuter = std::any_of( refs.begin(), refs.end(),
250 []( const TEXT_VAR_REF_KEY& k )
251 { return k.kind == TEXT_VAR_REF_KEY::KIND::CROSS_REF
252 && k.primary == wxT( "U1" ); } );
253
254 BOOST_CHECK( hasRow );
255 BOOST_CHECK( hasOuter );
256}
257
258
259BOOST_AUTO_TEST_CASE( Extract_MathExpressionInnerVar )
260{
261 // @{2*${X}} — the inner ${X} is what we depend on; the math wrapper itself
262 // is not a named variable source.
263 auto refs = ExtractTextVarReferences( wxT( "@{2*${X}}" ) );
264
265 bool hasX = std::any_of( refs.begin(), refs.end(),
266 []( const TEXT_VAR_REF_KEY& k )
267 { return k.primary == wxT( "X" ); } );
268
269 BOOST_CHECK( hasX );
270}
271
272
273// Use a test-local EDA_ITEM stand-in. EDA_ITEM is abstract (NotInSchematic, Clone,
274// GetClass pure virtual in subclasses) — we only need a unique pointer identity.
275namespace
276{
277struct FakeItem
278{
279 int id;
280};
281}
282
283
284BOOST_AUTO_TEST_CASE( Index_RegisterAndLookup )
285{
287 FakeItem item{ 1 };
288 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
289
290 std::vector<TEXT_VAR_REF_KEY> keys = {
291 TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) ),
292 TEXT_VAR_REF_KEY::FromToken( wxT( "SHEETNAME" ) )
293 };
294
295 index.Register( itemPtr, keys );
296
297 BOOST_CHECK_EQUAL( index.ItemCount(), 1u );
298 BOOST_CHECK_EQUAL( index.DependentCount( keys[0] ), 1u );
299 BOOST_CHECK_EQUAL( index.DependentCount( keys[1] ), 1u );
300
301 std::vector<EDA_ITEM*> found;
302 index.ForEachDependent( keys[0], [&]( EDA_ITEM* i ) { found.push_back( i ); } );
303
304 BOOST_REQUIRE_EQUAL( found.size(), 1u );
305 BOOST_CHECK_EQUAL( found[0], itemPtr );
306}
307
308
309BOOST_AUTO_TEST_CASE( Index_ReRegisterReplaces )
310{
312 FakeItem item{ 1 };
313 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
314
315 TEXT_VAR_REF_KEY oldKey = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) );
316 TEXT_VAR_REF_KEY newKey = TEXT_VAR_REF_KEY::FromToken( wxT( "U2:MPN" ) );
317
318 index.Register( itemPtr, { oldKey } );
319 BOOST_CHECK_EQUAL( index.DependentCount( oldKey ), 1u );
320
321 index.Register( itemPtr, { newKey } );
322 BOOST_CHECK_EQUAL( index.DependentCount( oldKey ), 0u );
323 BOOST_CHECK_EQUAL( index.DependentCount( newKey ), 1u );
324 BOOST_CHECK_EQUAL( index.ItemCount(), 1u );
325}
326
327
328BOOST_AUTO_TEST_CASE( Index_Unregister )
329{
331 FakeItem item{ 1 };
332 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
333
335 index.Register( itemPtr, { key } );
336 BOOST_CHECK_EQUAL( index.DependentCount( key ), 1u );
337
338 index.Unregister( itemPtr );
339 BOOST_CHECK_EQUAL( index.DependentCount( key ), 0u );
340 BOOST_CHECK_EQUAL( index.ItemCount(), 0u );
341
342 // Unregister is idempotent — second call is a no-op.
343 index.Unregister( itemPtr );
344 BOOST_CHECK_EQUAL( index.ItemCount(), 0u );
345}
346
347
348BOOST_AUTO_TEST_CASE( Index_MultipleItemsShareKey )
349{
351 FakeItem a{ 1 };
352 FakeItem b{ 2 };
353 FakeItem c{ 3 };
354 EDA_ITEM* aPtr = reinterpret_cast<EDA_ITEM*>( &a );
355 EDA_ITEM* bPtr = reinterpret_cast<EDA_ITEM*>( &b );
356 EDA_ITEM* cPtr = reinterpret_cast<EDA_ITEM*>( &c );
357
358 TEXT_VAR_REF_KEY shared = TEXT_VAR_REF_KEY::FromToken( wxT( "R1:VALUE" ) );
359 TEXT_VAR_REF_KEY other = TEXT_VAR_REF_KEY::FromToken( wxT( "SHEETNAME" ) );
360
361 index.Register( aPtr, { shared } );
362 index.Register( bPtr, { shared, other } );
363 index.Register( cPtr, { other } );
364
365 BOOST_CHECK_EQUAL( index.DependentCount( shared ), 2u );
366 BOOST_CHECK_EQUAL( index.DependentCount( other ), 2u );
367 BOOST_CHECK_EQUAL( index.ItemCount(), 3u );
368
369 // Removing one dependent leaves the other.
370 index.Unregister( aPtr );
371 BOOST_CHECK_EQUAL( index.DependentCount( shared ), 1u );
372
373 std::vector<EDA_ITEM*> remaining;
374 index.ForEachDependent( shared, [&]( EDA_ITEM* i ) { remaining.push_back( i ); } );
375 BOOST_REQUIRE_EQUAL( remaining.size(), 1u );
376 BOOST_CHECK_EQUAL( remaining[0], bPtr );
377}
378
379
380BOOST_AUTO_TEST_CASE( Index_ClearDropsEverything )
381{
383 FakeItem a{ 1 };
384 FakeItem b{ 2 };
385 EDA_ITEM* aPtr = reinterpret_cast<EDA_ITEM*>( &a );
386 EDA_ITEM* bPtr = reinterpret_cast<EDA_ITEM*>( &b );
387
390
391 index.Register( aPtr, { k1 } );
392 index.Register( bPtr, { k2 } );
393
394 index.Clear();
395
396 BOOST_CHECK_EQUAL( index.ItemCount(), 0u );
397 BOOST_CHECK_EQUAL( index.DependentCount( k1 ), 0u );
398 BOOST_CHECK_EQUAL( index.DependentCount( k2 ), 0u );
399}
400
401
402BOOST_AUTO_TEST_CASE( Index_EmptyRegistrationIsUnregister )
403{
405 FakeItem item{ 1 };
406 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
407
409
410 index.Register( itemPtr, { key } );
411 BOOST_CHECK_EQUAL( index.DependentCount( key ), 1u );
412
413 // Registering with an empty key vector clears the item's edges.
414 index.Register( itemPtr, {} );
415 BOOST_CHECK_EQUAL( index.DependentCount( key ), 0u );
416 BOOST_CHECK_EQUAL( index.ItemCount(), 0u );
417}
418
419
420// Tracker-level tests use direct index manipulation rather than exercising the
421// OnItemsAdded() entry point, because that entry point dynamic_cast<>s to
422// EDA_TEXT* which requires EDA_ITEM's typeinfo across a DSO boundary the
423// standalone qa_common executable does not see. Integration tests that wire a
424// real BOARD_LISTENER / SCHEMATIC_LISTENER live with the module tests.
425
426
427BOOST_AUTO_TEST_CASE( Tracker_InvalidateKeyFansOutToAllDependents )
428{
429 TEXT_VAR_TRACKER tracker;
430 FakeItem a{ 1 };
431 FakeItem b{ 2 };
432 FakeItem unrelated{ 3 };
433
434 EDA_ITEM* aPtr = reinterpret_cast<EDA_ITEM*>( &a );
435 EDA_ITEM* bPtr = reinterpret_cast<EDA_ITEM*>( &b );
436 EDA_ITEM* unrelatedPtr = reinterpret_cast<EDA_ITEM*>( &unrelated );
437
438 TEXT_VAR_REF_KEY shared = TEXT_VAR_REF_KEY::FromToken( wxT( "SHEETNAME" ) );
439 TEXT_VAR_REF_KEY other = TEXT_VAR_REF_KEY::FromToken( wxT( "OTHER" ) );
440
441 tracker.Index().Register( aPtr, { shared } );
442 tracker.Index().Register( bPtr, { shared } );
443 tracker.Index().Register( unrelatedPtr, { other } );
444
445 std::vector<EDA_ITEM*> hit;
446 (void) tracker.AddInvalidateListener(
447 [&]( EDA_ITEM* item, const TEXT_VAR_REF_KEY& ) { hit.push_back( item ); } );
448
449 tracker.InvalidateKey( shared );
450
451 BOOST_CHECK_EQUAL( hit.size(), 2u );
452 BOOST_CHECK( std::find( hit.begin(), hit.end(), aPtr ) != hit.end() );
453 BOOST_CHECK( std::find( hit.begin(), hit.end(), bPtr ) != hit.end() );
454 BOOST_CHECK( std::find( hit.begin(), hit.end(), unrelatedPtr ) == hit.end() );
455}
456
457
458BOOST_AUTO_TEST_CASE( Tracker_SourceChangeFansOutExtractedKeys )
459{
460 TEXT_VAR_TRACKER tracker;
461 FakeItem dependent{ 1 };
462 FakeItem source{ 2 };
463
464 EDA_ITEM* dependentPtr = reinterpret_cast<EDA_ITEM*>( &dependent );
465 EDA_ITEM* sourcePtr = reinterpret_cast<EDA_ITEM*>( &source );
466
467 TEXT_VAR_REF_KEY crossRef = TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) );
468 tracker.Index().Register( dependentPtr, { crossRef } );
469
470 tracker.SetSourceKeyExtractor(
471 [&]( EDA_ITEM* item ) -> std::vector<TEXT_VAR_REF_KEY>
472 {
473 if( item == sourcePtr )
474 return { crossRef };
475
476 return {};
477 } );
478
479 std::vector<std::pair<EDA_ITEM*, TEXT_VAR_REF_KEY>> invalidations;
480 (void) tracker.AddInvalidateListener(
481 [&]( EDA_ITEM* item, const TEXT_VAR_REF_KEY& key )
482 { invalidations.emplace_back( item, key ); } );
483
484 // HandleItemChanged re-registers (we pass empty keys since source is not
485 // itself a dependent) then fans out source keys via the extractor.
486 tracker.HandleItemChanged( sourcePtr, {} );
487
488 BOOST_REQUIRE_EQUAL( invalidations.size(), 1u );
489 BOOST_CHECK_EQUAL( invalidations[0].first, dependentPtr );
490 BOOST_CHECK( invalidations[0].second == crossRef );
491}
492
493
494BOOST_AUTO_TEST_CASE( Tracker_NoInvalidateWithoutCallback )
495{
496 TEXT_VAR_TRACKER tracker;
497 FakeItem item{ 1 };
498 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
499
500 tracker.Index().Register( itemPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "X" ) ) } );
501
502 // Without a registered invalidate callback, InvalidateKey is a no-op and
503 // must not crash.
504 tracker.InvalidateKey( TEXT_VAR_REF_KEY::FromToken( wxT( "X" ) ) );
505 BOOST_CHECK_EQUAL( tracker.Index().ItemCount(), 1u );
506}
507
508
509BOOST_AUTO_TEST_CASE( Tracker_InvalidateVariantScopedFiresCrossRefAndLocal )
510{
511 TEXT_VAR_TRACKER tracker;
512 FakeItem crossDep{ 1 };
513 FakeItem localDep{ 2 };
514 FakeItem titleDep{ 3 };
515
516 EDA_ITEM* crossPtr = reinterpret_cast<EDA_ITEM*>( &crossDep );
517 EDA_ITEM* localPtr = reinterpret_cast<EDA_ITEM*>( &localDep );
518 EDA_ITEM* titlePtr = reinterpret_cast<EDA_ITEM*>( &titleDep );
519
520 tracker.Index().Register( crossPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) ) } );
521 tracker.Index().Register( localPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "VALUE" ) ) } );
522 tracker.Index().Register( titlePtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) } );
523
524 std::vector<EDA_ITEM*> hit;
525 (void) tracker.AddInvalidateListener(
526 [&]( EDA_ITEM* item, const TEXT_VAR_REF_KEY& ) { hit.push_back( item ); } );
527
528 // Variant switches change ${REFDES:FIELD} and own-field values but do
529 // NOT affect title-block sources.
530 tracker.InvalidateVariantScoped();
531
532 BOOST_CHECK_EQUAL( hit.size(), 2u );
533 BOOST_CHECK( std::find( hit.begin(), hit.end(), crossPtr ) != hit.end() );
534 BOOST_CHECK( std::find( hit.begin(), hit.end(), localPtr ) != hit.end() );
535 BOOST_CHECK( std::find( hit.begin(), hit.end(), titlePtr ) == hit.end() );
536}
537
538
539BOOST_AUTO_TEST_CASE( Tracker_InvalidateByKindFiltersKeys )
540{
541 TEXT_VAR_TRACKER tracker;
542 FakeItem titleDep{ 1 };
543 FakeItem crossDep{ 2 };
544
545 EDA_ITEM* tPtr = reinterpret_cast<EDA_ITEM*>( &titleDep );
546 EDA_ITEM* cPtr = reinterpret_cast<EDA_ITEM*>( &crossDep );
547
548 tracker.Index().Register( tPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) } );
549 tracker.Index().Register( cPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "U1:VALUE" ) ) } );
550
551 std::vector<EDA_ITEM*> hit;
552 (void) tracker.AddInvalidateListener(
553 [&]( EDA_ITEM* item, const TEXT_VAR_REF_KEY& ) { hit.push_back( item ); } );
554
555 // A TITLE_BLOCK/SPECIAL-scoped fan-out must invalidate the title dep and
556 // leave the cross-ref dep alone — those are driven by per-item commits.
559
560 BOOST_REQUIRE_EQUAL( hit.size(), 1u );
561 BOOST_CHECK_EQUAL( hit[0], tPtr );
562}
563
564
565BOOST_AUTO_TEST_CASE( Index_GetRegisteredKeysEnumeratesAll )
566{
568 FakeItem a{ 1 };
569 FakeItem b{ 2 };
570
571 EDA_ITEM* aPtr = reinterpret_cast<EDA_ITEM*>( &a );
572 EDA_ITEM* bPtr = reinterpret_cast<EDA_ITEM*>( &b );
573
577
578 index.Register( aPtr, { k1, k2 } );
579 index.Register( bPtr, { k2, k3 } );
580
581 std::vector<TEXT_VAR_REF_KEY> keys = index.GetRegisteredKeys();
582 BOOST_CHECK_EQUAL( keys.size(), 3u ); // k2 is shared, counted once
583}
584
585
586BOOST_AUTO_TEST_CASE( Tracker_ClearDropsIndex )
587{
588 TEXT_VAR_TRACKER tracker;
589 FakeItem item{ 1 };
590 EDA_ITEM* itemPtr = reinterpret_cast<EDA_ITEM*>( &item );
591
592 tracker.Index().Register( itemPtr, { TEXT_VAR_REF_KEY::FromToken( wxT( "X" ) ) } );
593 BOOST_CHECK_EQUAL( tracker.Index().ItemCount(), 1u );
594
595 tracker.Clear();
596 BOOST_CHECK_EQUAL( tracker.Index().ItemCount(), 0u );
597}
598
599
int index
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:100
Bidirectional index mapping TEXT_VAR_REF_KEY → dependent items and item → keys.
void Register(EDA_ITEM *aItem, const std::vector< TEXT_VAR_REF_KEY > &aKeys)
Replace the key set for aItem with aKeys.
Coordinates the dependency index with change notifications.
void HandleItemChanged(EDA_ITEM *aItem, const std::vector< TEXT_VAR_REF_KEY > &aUpdatedKeys)
Handle a composite change for aItem: register its updated keys and fan out invalidation for every key...
void InvalidateVariantScoped()
Invalidate every source whose resolved value can differ between variants: CROSS_REF (footprint/symbol...
void SetSourceKeyExtractor(SourceKeyExtractor aExtractor)
ListenerHandle AddInvalidateListener(InvalidateCallback aCallback)
Register a listener that fires for every invalidation.
void InvalidateByKind(std::initializer_list< TEXT_VAR_REF_KEY::KIND > aKinds)
Fan out invalidation for every registered key whose KIND is one of the supplied set.
TEXT_VAR_DEPENDENCY_INDEX & Index()
void InvalidateKey(const TEXT_VAR_REF_KEY &aKey)
Fan out invalidation for a single explicit key — used when a non-item source changes (e....
std::vector< TEXT_VAR_REF_KEY > ExtractTextVarReferences(const wxString &aSource)
Lex-scan aSource and return every ${...} reference that appears, without resolving.
Definition common.cpp:435
The common library.
Identifies a single resolvable source that a text item's ${...} reference depends on.
static TEXT_VAR_REF_KEY FromToken(const wxString &aToken)
Parse a raw token (the text between ${ and }) into a key using lexical classification only — no looku...
KIND
Categorizes a reference by the source that will produce its value.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(KeyFromToken_Plain)