spec-up-t 1.6.1 → 1.6.3-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,577 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ /**
5
+ * @fileoverview Unit tests for validate-external-refs.js
6
+ * Tests the utility functions used for validating external references (xrefs and trefs).
7
+ *
8
+ * Note: The main validation flow involves network fetches and is tested via integration tests.
9
+ * These unit tests cover the synchronous utility functions.
10
+ */
11
+
12
+ // Mock the global allXTrefs before requiring the module
13
+ global.allXTrefs = {
14
+ xtrefs: [
15
+ {
16
+ externalSpec: 'TestSpec',
17
+ term: 'test-term',
18
+ ghPageUrl: 'https://example.github.io/test-spec/',
19
+ content: '<dd><p>This is a test definition.</p></dd>',
20
+ sourceFiles: [{ file: 'test.md', type: 'tref' }]
21
+ },
22
+ {
23
+ externalSpec: 'TestSpec',
24
+ term: 'another-term',
25
+ ghPageUrl: 'https://example.github.io/test-spec/',
26
+ content: '<dd><p>Another definition here.</p></dd>',
27
+ sourceFiles: [{ file: 'test.md', type: 'xref' }]
28
+ }
29
+ ]
30
+ };
31
+
32
+ // Since the file auto-initializes, we need to mock DOM ready state
33
+ Object.defineProperty(document, 'readyState', {
34
+ get: () => 'complete'
35
+ });
36
+
37
+ // Read and evaluate the source file to get the functions
38
+ const fs = require('fs');
39
+ const path = require('path');
40
+
41
+ // We'll test the functions by extracting their logic
42
+ // Since the module doesn't export, we test via DOM manipulation
43
+
44
+ describe('validate-external-refs', () => {
45
+ beforeEach(() => {
46
+ document.body.innerHTML = '';
47
+ // Clear any event listeners by resetting the body
48
+ jest.clearAllMocks();
49
+ });
50
+
51
+ afterEach(() => {
52
+ document.body.innerHTML = '';
53
+ });
54
+
55
+ describe('normalizeContent helper logic', () => {
56
+ /**
57
+ * Tests normalization of HTML content for comparison
58
+ * This replicates the normalizeContent function logic
59
+ * Note: In production, markdown-it processing is included
60
+ */
61
+ function normalizeContent(html) {
62
+ if (!html) return '';
63
+ // In tests, we skip markdown-it since it may not be available
64
+ const tempDiv = document.createElement('div');
65
+ tempDiv.innerHTML = html;
66
+ let text = tempDiv.textContent
67
+ .toLowerCase()
68
+ .replace(/\s+/g, ' ')
69
+ .trim();
70
+ text = text.replace(/[\u200B-\u200D\uFEFF]/g, '');
71
+ text = text.replace(/\s*([.,;:!?])\s*/g, '$1 ');
72
+ text = text.trim();
73
+ return text;
74
+ }
75
+
76
+ // Test: Does normalization handle empty input?
77
+ it('should return empty string for empty input', () => {
78
+ expect(normalizeContent('')).toBe('');
79
+ expect(normalizeContent(null)).toBe('');
80
+ expect(normalizeContent(undefined)).toBe('');
81
+ });
82
+
83
+ // Test: Does normalization extract text from HTML?
84
+ it('should extract text content from HTML', () => {
85
+ const html = '<dd><p>This is a <strong>test</strong> definition.</p></dd>';
86
+ expect(normalizeContent(html)).toBe('this is a test definition.');
87
+ });
88
+
89
+ // Test: Does normalization collapse whitespace?
90
+ it('should normalize whitespace', () => {
91
+ const html = '<p>Multiple spaces\nand\nnewlines</p>';
92
+ expect(normalizeContent(html)).toBe('multiple spaces and newlines');
93
+ });
94
+
95
+ // Test: Does normalization convert to lowercase?
96
+ it('should convert to lowercase', () => {
97
+ const html = '<p>UPPERCASE and MixedCase</p>';
98
+ expect(normalizeContent(html)).toBe('uppercase and mixedcase');
99
+ });
100
+
101
+ // Test: Does normalization handle punctuation spacing?
102
+ it('should normalize punctuation spacing', () => {
103
+ const html = '<p>Word,word . another</p>';
104
+ expect(normalizeContent(html)).toBe('word, word. another');
105
+ });
106
+
107
+ // Test: Does normalization handle identical HTML with different formatting?
108
+ it('should normalize identically formatted content', () => {
109
+ const html1 = '<p>This is a test.</p>';
110
+ const html2 = '<p>This is a test.</p>';
111
+ expect(normalizeContent(html1)).toBe(normalizeContent(html2));
112
+ });
113
+ });
114
+
115
+ describe('extractTermsFromHtml helper logic', () => {
116
+ /**
117
+ * Tests term extraction from HTML content
118
+ * This replicates the extractTermsFromHtml function logic
119
+ */
120
+ function extractTermsFromHtml(html) {
121
+ const terms = new Map();
122
+ const parser = new DOMParser();
123
+ const doc = parser.parseFromString(html, 'text/html');
124
+ const termElements = doc.querySelectorAll('dl.terms-and-definitions-list dt');
125
+
126
+ termElements.forEach(dt => {
127
+ const termSpan = dt.querySelector('[id^="term:"]');
128
+ if (!termSpan) return;
129
+
130
+ const termId = termSpan.id;
131
+ const termName = termId.split(':').pop();
132
+
133
+ // Collect ALL consecutive dd elements (not just the first one)
134
+ const ddElements = [];
135
+ let definitionContent = '';
136
+ let rawContent = '';
137
+ let sibling = dt.nextElementSibling;
138
+
139
+ // Collect all consecutive dd elements, skipping meta-info wrappers
140
+ while (sibling && sibling.tagName === 'DD') {
141
+ if (!sibling.classList.contains('meta-info-content-wrapper')) {
142
+ ddElements.push(sibling);
143
+ }
144
+ sibling = sibling.nextElementSibling;
145
+ }
146
+
147
+ if (ddElements.length > 0) {
148
+ // Combine all dd elements' raw HTML
149
+ rawContent = ddElements.map(dd => dd.outerHTML).join('\n');
150
+ // Combine all dd elements' text content
151
+ definitionContent = ddElements.map(dd => dd.textContent).join('\n');
152
+ }
153
+
154
+ terms.set(termName.toLowerCase(), {
155
+ content: definitionContent.trim(),
156
+ rawContent: rawContent,
157
+ termId: termId
158
+ });
159
+ });
160
+
161
+ return terms;
162
+ }
163
+
164
+ // Test: Does extraction handle empty HTML?
165
+ it('should return empty map for empty HTML', () => {
166
+ const terms = extractTermsFromHtml('');
167
+ expect(terms.size).toBe(0);
168
+ });
169
+
170
+ // Test: Does extraction handle HTML without terms?
171
+ it('should return empty map for HTML without term definitions', () => {
172
+ const html = '<div><p>Just some content</p></div>';
173
+ const terms = extractTermsFromHtml(html);
174
+ expect(terms.size).toBe(0);
175
+ });
176
+
177
+ // Test: Does extraction correctly parse term definitions?
178
+ it('should extract terms from valid term list HTML', () => {
179
+ const html = `
180
+ <dl class="terms-and-definitions-list">
181
+ <dt><span id="term:test-term">test-term</span></dt>
182
+ <dd><p>Definition of test term.</p></dd>
183
+ <dt><span id="term:another-term">another-term</span></dt>
184
+ <dd><p>Another definition.</p></dd>
185
+ </dl>
186
+ `;
187
+ const terms = extractTermsFromHtml(html);
188
+ expect(terms.size).toBe(2);
189
+ expect(terms.has('test-term')).toBe(true);
190
+ expect(terms.has('another-term')).toBe(true);
191
+ });
192
+
193
+ // Test: Does extraction capture definition content?
194
+ it('should capture definition content correctly', () => {
195
+ const html = `
196
+ <dl class="terms-and-definitions-list">
197
+ <dt><span id="term:my-term">my-term</span></dt>
198
+ <dd><p>This is the definition.</p></dd>
199
+ </dl>
200
+ `;
201
+ const terms = extractTermsFromHtml(html);
202
+ const term = terms.get('my-term');
203
+ expect(term.content).toContain('This is the definition.');
204
+ });
205
+
206
+ // Test: Does extraction skip meta-info wrappers?
207
+ it('should skip meta-info-content-wrapper elements', () => {
208
+ const html = `
209
+ <dl class="terms-and-definitions-list">
210
+ <dt><span id="term:meta-term">meta-term</span></dt>
211
+ <dd class="meta-info-content-wrapper">Meta info</dd>
212
+ <dd><p>Real definition.</p></dd>
213
+ </dl>
214
+ `;
215
+ const terms = extractTermsFromHtml(html);
216
+ const term = terms.get('meta-term');
217
+ expect(term.content).not.toContain('Meta info');
218
+ expect(term.content).toContain('Real definition.');
219
+ });
220
+
221
+ // Test: Does extraction include all consecutive dd elements?
222
+ it('should extract all consecutive dd elements', () => {
223
+ const html = `
224
+ <dl class="terms-and-definitions-list">
225
+ <dt><span id="term:multi-dd">multi-dd</span></dt>
226
+ <dd><p>First part.</p></dd>
227
+ <dd><p>Second part.</p></dd>
228
+ </dl>
229
+ `;
230
+ const terms = extractTermsFromHtml(html);
231
+ const term = terms.get('multi-dd');
232
+ expect(term.content).toContain('First part.');
233
+ expect(term.content).toContain('Second part.'); // Should include all dd elements
234
+ });
235
+ });
236
+
237
+ describe('createIndicator helper logic', () => {
238
+ /**
239
+ * Tests indicator creation logic
240
+ */
241
+ function createIndicator(type, details = {}) {
242
+ const VALIDATOR_CONFIG = {
243
+ classes: {
244
+ indicator: 'external-ref-validation-indicator',
245
+ missing: 'external-ref-missing',
246
+ changed: 'external-ref-changed',
247
+ valid: 'external-ref-valid',
248
+ error: 'external-ref-error'
249
+ },
250
+ labels: {
251
+ missing: '⚠️ Term not found',
252
+ changed: '🔄 Definition changed',
253
+ error: '❌ Could not verify',
254
+ valid: '✓ Verified'
255
+ }
256
+ };
257
+
258
+ const indicator = document.createElement('span');
259
+ indicator.classList.add(
260
+ VALIDATOR_CONFIG.classes.indicator,
261
+ VALIDATOR_CONFIG.classes[type]
262
+ );
263
+
264
+ const labelText = details.message || VALIDATOR_CONFIG.labels[type];
265
+ indicator.setAttribute('title', labelText);
266
+
267
+ const iconSpan = document.createElement('span');
268
+ iconSpan.classList.add('indicator-icon');
269
+ iconSpan.textContent = VALIDATOR_CONFIG.labels[type].split(' ')[0];
270
+ indicator.appendChild(iconSpan);
271
+
272
+ return indicator;
273
+ }
274
+
275
+ // Test: Does indicator have correct classes for missing type?
276
+ it('should create indicator with correct classes for missing type', () => {
277
+ const indicator = createIndicator('missing');
278
+ expect(indicator.classList.contains('external-ref-validation-indicator')).toBe(true);
279
+ expect(indicator.classList.contains('external-ref-missing')).toBe(true);
280
+ });
281
+
282
+ // Test: Does indicator have correct classes for changed type?
283
+ it('should create indicator with correct classes for changed type', () => {
284
+ const indicator = createIndicator('changed');
285
+ expect(indicator.classList.contains('external-ref-validation-indicator')).toBe(true);
286
+ expect(indicator.classList.contains('external-ref-changed')).toBe(true);
287
+ });
288
+
289
+ // Test: Does indicator have correct classes for error type?
290
+ it('should create indicator with correct classes for error type', () => {
291
+ const indicator = createIndicator('error');
292
+ expect(indicator.classList.contains('external-ref-validation-indicator')).toBe(true);
293
+ expect(indicator.classList.contains('external-ref-error')).toBe(true);
294
+ });
295
+
296
+ // Test: Does indicator have correct classes for valid type?
297
+ it('should create indicator with correct classes for valid type', () => {
298
+ const indicator = createIndicator('valid');
299
+ expect(indicator.classList.contains('external-ref-validation-indicator')).toBe(true);
300
+ expect(indicator.classList.contains('external-ref-valid')).toBe(true);
301
+ });
302
+
303
+ // Test: Does indicator have correct title attribute?
304
+ it('should set appropriate title for tooltip', () => {
305
+ const indicator = createIndicator('missing');
306
+ expect(indicator.getAttribute('title')).toBe('⚠️ Term not found');
307
+ });
308
+
309
+ // Test: Does indicator contain icon element?
310
+ it('should contain an icon element with emoji', () => {
311
+ const indicator = createIndicator('changed');
312
+ const iconSpan = indicator.querySelector('.indicator-icon');
313
+ expect(iconSpan).not.toBeNull();
314
+ expect(iconSpan.textContent).toBe('🔄');
315
+ });
316
+ });
317
+
318
+ describe('truncateText helper logic', () => {
319
+ function truncateText(text, maxLength) {
320
+ if (!text || text.length <= maxLength) return text || '';
321
+ return text.substring(0, maxLength) + '...';
322
+ }
323
+
324
+ // Test: Does truncation handle empty input?
325
+ it('should return empty string for null or undefined', () => {
326
+ expect(truncateText(null, 10)).toBe('');
327
+ expect(truncateText(undefined, 10)).toBe('');
328
+ });
329
+
330
+ // Test: Does truncation preserve short text?
331
+ it('should not truncate text shorter than max length', () => {
332
+ expect(truncateText('short', 10)).toBe('short');
333
+ });
334
+
335
+ // Test: Does truncation work on long text?
336
+ it('should truncate text longer than max length', () => {
337
+ expect(truncateText('this is a long text', 10)).toBe('this is a ...');
338
+ });
339
+
340
+ // Test: Does truncation handle exact length?
341
+ it('should not truncate text exactly at max length', () => {
342
+ expect(truncateText('exactly10!', 10)).toBe('exactly10!');
343
+ });
344
+ });
345
+
346
+ describe('similarity calculation logic', () => {
347
+ /**
348
+ * Tests similarity calculation between two strings
349
+ */
350
+ function levenshteinDistance(str1, str2) {
351
+ const matrix = [];
352
+
353
+ for (let i = 0; i <= str2.length; i++) {
354
+ matrix[i] = [i];
355
+ }
356
+
357
+ for (let j = 0; j <= str1.length; j++) {
358
+ matrix[0][j] = j;
359
+ }
360
+
361
+ for (let i = 1; i <= str2.length; i++) {
362
+ for (let j = 1; j <= str1.length; j++) {
363
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
364
+ matrix[i][j] = matrix[i - 1][j - 1];
365
+ } else {
366
+ matrix[i][j] = Math.min(
367
+ matrix[i - 1][j - 1] + 1,
368
+ matrix[i][j - 1] + 1,
369
+ matrix[i - 1][j] + 1
370
+ );
371
+ }
372
+ }
373
+ }
374
+
375
+ return matrix[str2.length][str1.length];
376
+ }
377
+
378
+ function calculateSimilarity(str1, str2) {
379
+ if (str1 === str2) return 1;
380
+ if (!str1 || !str2) return 0;
381
+
382
+ const longer = str1.length > str2.length ? str1 : str2;
383
+ const shorter = str1.length > str2.length ? str2 : str1;
384
+
385
+ if (longer.length === 0) return 1;
386
+
387
+ const editDistance = levenshteinDistance(shorter, longer);
388
+ return (longer.length - editDistance) / longer.length;
389
+ }
390
+
391
+ // Test: Do identical strings have 100% similarity?
392
+ it('should return 1 for identical strings', () => {
393
+ expect(calculateSimilarity('test', 'test')).toBe(1);
394
+ expect(calculateSimilarity('hello world', 'hello world')).toBe(1);
395
+ });
396
+
397
+ // Test: Do completely different strings have low similarity?
398
+ it('should return low similarity for very different strings', () => {
399
+ const similarity = calculateSimilarity('abc', 'xyz');
400
+ expect(similarity).toBeLessThan(0.5);
401
+ });
402
+
403
+ // Test: Does similarity work for slight variations?
404
+ it('should return high similarity for slight variations', () => {
405
+ const similarity = calculateSimilarity('hello world', 'hello world!');
406
+ expect(similarity).toBeGreaterThan(0.9);
407
+ });
408
+
409
+ // Test: Does similarity handle empty strings?
410
+ it('should return 0 for empty strings', () => {
411
+ expect(calculateSimilarity('', '')).toBe(1); // Both empty is 100% similar
412
+ expect(calculateSimilarity('test', '')).toBe(0);
413
+ expect(calculateSimilarity('', 'test')).toBe(0);
414
+ });
415
+
416
+ // Test: Does similarity threshold of 95% catch meaningful changes?
417
+ it('should detect significant changes below 95% threshold', () => {
418
+ const original = 'This is a long definition with many words about something important.';
419
+ const changed = 'This is completely different content about other things.';
420
+ const similarity = calculateSimilarity(original, changed);
421
+ expect(similarity).toBeLessThan(0.95); // Should trigger change indicator
422
+ });
423
+
424
+ // Test: Does similarity threshold of 95% ignore minor formatting?
425
+ it('should ignore minor formatting differences above 95% threshold', () => {
426
+ const original = 'This is a definition.';
427
+ const formatted = 'This is a definition. '; // Extra space
428
+ const similarity = calculateSimilarity(original.toLowerCase().trim(), formatted.toLowerCase().trim());
429
+ expect(similarity).toBeGreaterThanOrEqual(0.95); // Should NOT trigger change indicator
430
+ });
431
+ });
432
+
433
+ describe('truncateText helper logic', () => {
434
+ function truncateText(text, maxLength) {
435
+ if (!text || text.length <= maxLength) return text || '';
436
+ return text.substring(0, maxLength) + '...';
437
+ }
438
+
439
+ // Test: Does truncation handle empty input?
440
+ it('should return empty string for null or undefined', () => {
441
+ expect(truncateText(null, 10)).toBe('');
442
+ expect(truncateText(undefined, 10)).toBe('');
443
+ });
444
+
445
+ // Test: Does truncation preserve short text?
446
+ it('should not truncate text shorter than max length', () => {
447
+ expect(truncateText('short', 10)).toBe('short');
448
+ });
449
+
450
+ // Test: Does truncation work on long text?
451
+ it('should truncate text longer than max length', () => {
452
+ expect(truncateText('this is a long text', 10)).toBe('this is a ...');
453
+ });
454
+
455
+ // Test: Does truncation handle exact length?
456
+ it('should not truncate text exactly at max length', () => {
457
+ expect(truncateText('exactly10!', 10)).toBe('exactly10!');
458
+ });
459
+ });
460
+
461
+ describe('escapeHtml helper logic', () => {
462
+ function escapeHtml(text) {
463
+ const div = document.createElement('div');
464
+ div.textContent = text;
465
+ return div.innerHTML;
466
+ }
467
+
468
+ // Test: Does escaping handle normal text?
469
+ it('should return text unchanged for normal input', () => {
470
+ expect(escapeHtml('normal text')).toBe('normal text');
471
+ });
472
+
473
+ // Test: Does escaping convert HTML entities?
474
+ it('should escape HTML special characters', () => {
475
+ expect(escapeHtml('<script>alert("xss")</script>')).toBe(
476
+ '&lt;script&gt;alert("xss")&lt;/script&gt;'
477
+ );
478
+ });
479
+
480
+ // Test: Does escaping handle ampersands?
481
+ it('should escape ampersands', () => {
482
+ expect(escapeHtml('one & two')).toBe('one &amp; two');
483
+ });
484
+ });
485
+
486
+ describe('DOM element finding logic', () => {
487
+ beforeEach(() => {
488
+ document.body.innerHTML = `
489
+ <a class="x-term-reference"
490
+ data-local-href="#term:TestSpec:test-term"
491
+ href="https://example.github.io/test-spec/#term:test-term">
492
+ test-term
493
+ </a>
494
+ <dl class="terms-and-definitions-list">
495
+ <dt class="term-external">
496
+ <span class="term-external" data-original-term="tref-term">
497
+ tref-term
498
+ </span>
499
+ </dt>
500
+ </dl>
501
+ `;
502
+ });
503
+
504
+ // Test: Can xref elements be found in the DOM?
505
+ it('should find xref elements by class', () => {
506
+ const xrefs = document.querySelectorAll('a.x-term-reference');
507
+ expect(xrefs.length).toBe(1);
508
+ });
509
+
510
+ // Test: Can tref elements be found in the DOM?
511
+ it('should find tref elements by class', () => {
512
+ const trefs = document.querySelectorAll('dt.term-external');
513
+ expect(trefs.length).toBe(1);
514
+ });
515
+
516
+ // Test: Can data-local-href be parsed for spec and term?
517
+ it('should parse data-local-href for spec and term names', () => {
518
+ const xref = document.querySelector('a.x-term-reference');
519
+ const localHref = xref.getAttribute('data-local-href');
520
+ const match = localHref.match(/#term:([^:]+):(.+)/);
521
+ expect(match).not.toBeNull();
522
+ expect(match[1]).toBe('TestSpec');
523
+ expect(match[2]).toBe('test-term');
524
+ });
525
+
526
+ // Test: Can original term be retrieved from tref data attribute?
527
+ it('should retrieve original term from data-original-term', () => {
528
+ const termSpan = document.querySelector('[data-original-term]');
529
+ expect(termSpan.dataset.originalTerm).toBe('tref-term');
530
+ });
531
+ });
532
+
533
+ describe('indicator insertion logic', () => {
534
+ beforeEach(() => {
535
+ document.body.innerHTML = `
536
+ <a class="x-term-reference" id="test-xref">test</a>
537
+ <dt class="term-external" id="test-tref">
538
+ <span class="term-external">term</span>
539
+ </dt>
540
+ `;
541
+ });
542
+
543
+ // Test: Can indicators be inserted after xref elements?
544
+ it('should insert indicator after xref element', () => {
545
+ const xref = document.getElementById('test-xref');
546
+ const indicator = document.createElement('span');
547
+ indicator.classList.add('external-ref-validation-indicator');
548
+ xref.insertAdjacentElement('afterend', indicator);
549
+
550
+ expect(xref.nextElementSibling).toBe(indicator);
551
+ });
552
+
553
+ // Test: Can indicators be inserted into tref elements?
554
+ it('should insert indicator into tref span element', () => {
555
+ const termSpan = document.querySelector('dt.term-external span.term-external');
556
+ const indicator = document.createElement('span');
557
+ indicator.classList.add('external-ref-validation-indicator');
558
+ termSpan.appendChild(indicator);
559
+
560
+ expect(termSpan.querySelector('.external-ref-validation-indicator')).toBe(indicator);
561
+ });
562
+
563
+ // Test: Should prevent duplicate indicators
564
+ it('should not add duplicate indicators', () => {
565
+ const xref = document.getElementById('test-xref');
566
+
567
+ // Add first indicator
568
+ const indicator1 = document.createElement('span');
569
+ indicator1.classList.add('external-ref-validation-indicator');
570
+ xref.insertAdjacentElement('afterend', indicator1);
571
+
572
+ // Check for existing before adding second
573
+ const existing = xref.nextElementSibling?.classList.contains('external-ref-validation-indicator');
574
+ expect(existing).toBe(true);
575
+ });
576
+ });
577
+ });
@@ -29,6 +29,7 @@
29
29
  "assets/css/horizontal-scroll-hint.css",
30
30
  "assets/css/highlight-heading-plus-sibling-nodes.css",
31
31
  "assets/css/counter.css",
32
+ "assets/css/validate-external-refs.css",
32
33
 
33
34
  "assets/css/index.css"
34
35
  ],
@@ -84,6 +85,7 @@
84
85
  "assets/js/add-bootstrap-classes-to-images.js",
85
86
  "assets/js/image-full-size.js",
86
87
  "assets/js/highlight-heading-plus-sibling-nodes.js",
88
+ "assets/js/validate-external-refs.js",
87
89
  "assets/js/embedded-libraries/bootstrap.bundle.min.js"
88
90
  ]
89
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-up-t",
3
- "version": "1.6.1",
3
+ "version": "1.6.3-beta.1",
4
4
  "description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
5
5
  "main": "./index",
6
6
  "repository": {
@@ -1 +1,5 @@
1
1
  [//]: # (This file, named “terms-and-definitions-intro.md” is mandatory and should not be deleted. However, you can safely delete this comment and replace it with text of your choice.)
2
+
3
+ ## Terms and definitions
4
+
5
+ This is the terms and definitions section
@@ -1,3 +1,5 @@
1
- [[tref: KERISuite, composability ]]
1
+ [[tref: ExtRef1, greenhouse]]
2
2
 
3
- ~ Note: composability is not the same as compostability.
3
+ ~ Note: This is a tref example. The term "greenhouse" is imported from the ExtRef1 external glossary (focused on greenhouse and irrigation concepts).
4
+
5
+ ~ See also: [[xref: ExtRef2, propagation]] for plant propagation from ExtRef2.
@@ -0,0 +1,5 @@
1
+ [[tref: ExtRef2, dormancy]]
2
+
3
+ ~ Note: This is a tref example. The term "dormancy" is imported from the ExtRef2 external glossary (focused on plant lifecycle and propagation concepts).
4
+
5
+ ~ See also: [[xref: ExtRef1, perennial]] for perennial plants from ExtRef1.
@@ -0,0 +1,5 @@
1
+ [[tref: ExtRef1, greenhouse]]
2
+
3
+ ~ Note: This is a tref example. The term "greenhouse" is imported from the ExtRef1 external glossary (focused on greenhouse and irrigation concepts).
4
+
5
+ ~ See also: [[xref: ExtRef2, propagation]] for plant propagation from ExtRef2.
@@ -4,8 +4,10 @@
4
4
 
5
5
  ~ Refs examples: [[ref: Compost]], [[ref: Mulch]], [[ref: Fertilizer]].
6
6
 
7
- ~ Xref example: [[xref: KERISuite, composability ]]
7
+ ~ Xref examples from external gardening glossaries:
8
+ ~ - From ExtRef1 (greenhouse focus): [[xref: ExtRef1, greenhouse]], [[xref: ExtRef1, irrigation]]
9
+ ~ - From ExtRef2 (lifecycle focus): [[xref: ExtRef2, dormancy]], [[xref: ExtRef2, propagation]]
8
10
 
9
- ~ This Xref example does not work: [[xref: does-not-exist, Foo]]
11
+ ~ Note: The term "soil" exists in ExtRef1 and ExtRef2 with different definitions focused on their respective themes.
10
12
 
11
13
  ~ Soil quality affects water retention, nutrient availability, and plant health. Amending soil with compost or mulch can improve its structure and fertility. Fertilizer may be added to supplement nutrients as needed.