com.wallstop-studios.dxmessaging 2.1.5 → 2.1.6
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.
- package/.artifacts/SourceGenerators.Tests/obj/Debug/net9.0/WallstopStudios.DxMessaging.SourceGenerators.Tests.AssemblyInfo.cs +1 -1
- package/.cspell.json +4 -1
- package/.github/workflows/actionlint.yml +11 -1
- package/.github/workflows/csharpier-autofix.yml +34 -3
- package/.github/workflows/dotnet-tests.yml +13 -0
- package/.github/workflows/format-on-demand.yml +38 -44
- package/.github/workflows/json-format-check.yml +24 -0
- package/.github/workflows/lint-doc-links.yml +13 -0
- package/.github/workflows/markdown-json.yml +21 -4
- package/.github/workflows/markdown-link-text-check.yml +10 -0
- package/.github/workflows/markdown-link-validity.yml +10 -0
- package/.github/workflows/markdownlint.yml +7 -5
- package/.github/workflows/prettier-autofix.yml +67 -11
- package/.github/workflows/release-drafter.yml +2 -2
- package/.github/workflows/sync-wiki.yml +3 -3
- package/.github/workflows/yaml-format-lint.yml +26 -0
- package/.llm/context.md +113 -3
- package/.llm/skills/documentation/changelog-management.md +38 -0
- package/.llm/skills/documentation/documentation-style-guide.md +18 -0
- package/.llm/skills/documentation/documentation-update-workflow.md +2 -0
- package/.llm/skills/documentation/documentation-updates.md +2 -0
- package/.llm/skills/documentation/markdown-compatibility.md +476 -0
- package/.llm/skills/documentation/mermaid-theming.md +326 -0
- package/.llm/skills/documentation/mkdocs-navigation.md +290 -0
- package/.llm/skills/github-actions/git-renormalize-patterns.md +231 -0
- package/.llm/skills/github-actions/workflow-consistency.md +346 -0
- package/.llm/skills/index.md +53 -27
- package/.llm/skills/scripting/javascript-code-quality.md +417 -0
- package/.llm/skills/scripting/regex-documentation.md +461 -0
- package/.llm/skills/scripting/shell-best-practices.md +55 -4
- package/.llm/skills/scripting/validation-patterns.md +418 -0
- package/.llm/skills/specification.md +4 -1
- package/.llm/skills/testing/test-code-quality.md +243 -0
- package/.llm/skills/testing/test-production-code.md +348 -0
- package/CHANGELOG.md +11 -0
- package/README.md +0 -11
- package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef +1 -6
- package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef +1 -1
- package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef +1 -1
- package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef +1 -1
- package/coverage/clover.xml +216 -3
- package/coverage/clover.xml.meta +7 -7
- package/coverage/coverage-final.json +2 -1
- package/coverage/coverage-final.json.meta +7 -7
- package/coverage/lcov-report/base.css.meta +1 -1
- package/coverage/lcov-report/block-navigation.js.meta +1 -1
- package/coverage/lcov-report/favicon.png.meta +1 -1
- package/coverage/lcov-report/index.html +25 -10
- package/coverage/lcov-report/index.html.meta +7 -7
- package/coverage/lcov-report/prettify.css.meta +1 -1
- package/coverage/lcov-report/prettify.js.meta +1 -1
- package/coverage/lcov-report/sort-arrow-sprite.png.meta +1 -1
- package/coverage/lcov-report/sorter.js.meta +1 -1
- package/coverage/lcov-report/transform-docs-to-wiki.js.html +1 -1
- package/coverage/lcov-report/transform-docs-to-wiki.js.html.meta +7 -7
- package/coverage/lcov-report/vendor.meta +1 -1
- package/coverage/lcov-report.meta +8 -8
- package/coverage/lcov.info +365 -0
- package/coverage/lcov.info.meta +7 -7
- package/docs/architecture/design-and-architecture.md +0 -1
- package/docs/concepts/index.md +37 -0
- package/docs/concepts/index.md.meta +7 -0
- package/docs/concepts/interceptors-and-ordering.md +0 -2
- package/docs/concepts/mental-model.md +390 -0
- package/docs/concepts/mental-model.md.meta +7 -0
- package/docs/concepts/message-types.md +0 -1
- package/docs/getting-started/getting-started.md +1 -0
- package/docs/getting-started/index.md +6 -5
- package/docs/getting-started/overview.md +1 -0
- package/docs/getting-started/quick-start.md +2 -1
- package/docs/getting-started/visual-guide.md +4 -10
- package/docs/hooks.py +10 -1
- package/docs/images/DxMessaging-banner.svg +1 -1
- package/docs/index.md +7 -7
- package/docs/javascripts/mermaid-config.js +44 -4
- package/docs/reference/helpers.md +130 -154
- package/docs/reference/quick-reference.md +5 -1
- package/docs/reference/reference.md +124 -130
- package/mkdocs.yml +2 -0
- package/package.json +1 -1
- package/scripts/__tests__/generate-skills-index.test.js +397 -0
- package/scripts/__tests__/generate-skills-index.test.js.meta +7 -0
- package/scripts/__tests__/mermaid-config.test.js +467 -0
- package/scripts/__tests__/mermaid-config.test.js.meta +7 -0
- package/scripts/__tests__/validate-skills-optional-fields.test.js +1474 -0
- package/scripts/__tests__/validate-skills-optional-fields.test.js.meta +7 -0
- package/scripts/__tests__/validate-skills-required-fields.test.js +188 -0
- package/scripts/__tests__/validate-skills-required-fields.test.js.meta +7 -0
- package/scripts/__tests__/validate-skills-tags.test.js +353 -0
- package/scripts/__tests__/validate-skills-tags.test.js.meta +7 -0
- package/scripts/__tests__/validate-workflows.test.js +188 -0
- package/scripts/__tests__/validate-workflows.test.js.meta +7 -0
- package/scripts/generate-skills-index.js +88 -3
- package/scripts/validate-skills.js +230 -30
- package/scripts/validate-workflows.js +272 -0
- package/scripts/validate-workflows.js.meta +7 -0
- package/site/404.html +1 -1
- package/site/advanced/emit-shorthands/index.html +2 -2
- package/site/advanced/message-bus-providers/index.html +2 -2
- package/site/advanced/registration-builders/index.html +2 -2
- package/site/advanced/runtime-configuration/index.html +2 -2
- package/site/advanced/string-messages/index.html +2 -2
- package/site/advanced.meta +1 -1
- package/site/architecture/comparisons/index.html +2 -2
- package/site/architecture/design-and-architecture/index.html +2 -2
- package/site/architecture/performance/index.html +1 -1
- package/site/architecture.meta +1 -1
- package/site/concepts/index.html +1 -0
- package/site/concepts/index.html.meta +7 -0
- package/site/concepts/interceptors-and-ordering/index.html +4 -4
- package/site/concepts/listening-patterns/index.html +2 -2
- package/site/concepts/mental-model/index.html +146 -0
- package/site/concepts/mental-model/index.html.meta +7 -0
- package/site/concepts/mental-model.meta +8 -0
- package/site/concepts/message-types/index.html +2 -2
- package/site/concepts/targeting-and-context/index.html +2 -2
- package/site/concepts.meta +1 -1
- package/site/examples/end-to-end/index.html +2 -2
- package/site/examples/end-to-end-scene-transitions/index.html +2 -2
- package/site/examples.meta +1 -1
- package/site/getting-started/getting-started/index.html +3 -3
- package/site/getting-started/index.html +4 -4
- package/site/getting-started/install/index.html +3 -3
- package/site/getting-started/overview/index.html +2 -2
- package/site/getting-started/quick-start/index.html +2 -2
- package/site/getting-started/visual-guide/index.html +11 -11
- package/site/getting-started.meta +1 -1
- package/site/guides/advanced/index.html +2 -2
- package/site/guides/diagnostics/index.html +2 -2
- package/site/guides/migration-guide/index.html +2 -2
- package/site/guides/patterns/index.html +2 -2
- package/site/guides/testing/index.html +2 -2
- package/site/guides/unity-integration/index.html +2 -2
- package/site/guides.meta +1 -1
- package/site/hooks.py.meta +1 -1
- package/site/images/DxMessaging-banner.svg +119 -0
- package/site/images/DxMessaging-banner.svg.meta +7 -0
- package/site/images.meta +8 -0
- package/site/index.html +2 -2
- package/site/integrations/index.html +2 -2
- package/site/integrations/reflex/index.html +2 -2
- package/site/integrations/vcontainer/index.html +2 -2
- package/site/integrations/zenject/index.html +2 -2
- package/site/integrations.meta +1 -1
- package/site/javascripts/csharp-highlight.js.meta +7 -7
- package/site/javascripts/mermaid-config.js +4 -1
- package/site/javascripts/mermaid-config.js.meta +1 -1
- package/site/javascripts.meta +1 -1
- package/site/reference/compatibility/index.html +1 -1
- package/site/reference/faq/index.html +1 -1
- package/site/reference/glossary/index.html +2 -2
- package/site/reference/helpers/index.html +15 -15
- package/site/reference/quick-reference/index.html +3 -3
- package/site/reference/reference/index.html +37 -37
- package/site/reference/troubleshooting/index.html +1 -1
- package/site/reference.meta +1 -1
- package/site/search/search_index.json +1 -1
- package/site/sitemap.xml +46 -38
- package/site/sitemap.xml.gz +0 -0
- package/site/stylesheets/extra.css.meta +1 -1
- package/site/stylesheets.meta +1 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Validation Patterns and Duplicate Warning Prevention"
|
|
3
|
+
id: "validation-patterns"
|
|
4
|
+
category: "scripting"
|
|
5
|
+
version: "1.1.0"
|
|
6
|
+
created: "2026-01-30"
|
|
7
|
+
updated: "2026-01-30"
|
|
8
|
+
|
|
9
|
+
source:
|
|
10
|
+
repository: "wallstop/DxMessaging"
|
|
11
|
+
files:
|
|
12
|
+
- path: "scripts/validate-skills.js"
|
|
13
|
+
- path: "scripts/__tests__/validate-skills-optional-fields.test.js"
|
|
14
|
+
url: "https://github.com/wallstop/DxMessaging"
|
|
15
|
+
|
|
16
|
+
tags:
|
|
17
|
+
- "validation"
|
|
18
|
+
- "javascript"
|
|
19
|
+
- "error-handling"
|
|
20
|
+
- "duplicate-warnings"
|
|
21
|
+
- "enum-validation"
|
|
22
|
+
- "optional-fields"
|
|
23
|
+
- "testing"
|
|
24
|
+
- "truthiness"
|
|
25
|
+
- "type-coercion"
|
|
26
|
+
|
|
27
|
+
complexity:
|
|
28
|
+
level: "intermediate"
|
|
29
|
+
reasoning: "Requires understanding of validation flow, conditional logic ordering, and JavaScript type coercion"
|
|
30
|
+
|
|
31
|
+
impact:
|
|
32
|
+
performance:
|
|
33
|
+
rating: "none"
|
|
34
|
+
details: "Validation patterns only; no runtime performance impact"
|
|
35
|
+
maintainability:
|
|
36
|
+
rating: "high"
|
|
37
|
+
details: "Prevents confusing duplicate warnings that obscure actual issues"
|
|
38
|
+
testability:
|
|
39
|
+
rating: "high"
|
|
40
|
+
details: "Clear validation logic is easier to test exhaustively"
|
|
41
|
+
|
|
42
|
+
prerequisites:
|
|
43
|
+
- "JavaScript fundamentals"
|
|
44
|
+
- "Understanding of null/undefined distinction"
|
|
45
|
+
- "Basic validation concepts"
|
|
46
|
+
|
|
47
|
+
dependencies:
|
|
48
|
+
skills:
|
|
49
|
+
- "javascript-code-quality"
|
|
50
|
+
|
|
51
|
+
applies_to:
|
|
52
|
+
languages:
|
|
53
|
+
- "JavaScript"
|
|
54
|
+
- "TypeScript"
|
|
55
|
+
frameworks:
|
|
56
|
+
- "Node.js"
|
|
57
|
+
|
|
58
|
+
aliases:
|
|
59
|
+
- "duplicate warning prevention"
|
|
60
|
+
- "validation deduplication"
|
|
61
|
+
- "enum validation"
|
|
62
|
+
- "presence vs truthiness"
|
|
63
|
+
|
|
64
|
+
related:
|
|
65
|
+
- "javascript-code-quality"
|
|
66
|
+
- "comprehensive-test-coverage"
|
|
67
|
+
|
|
68
|
+
status: "stable"
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# Validation Patterns and Duplicate Warning Prevention
|
|
72
|
+
|
|
73
|
+
> **One-line summary**: Structure validation logic to produce exactly one warning per issue,
|
|
74
|
+
> not duplicates from multiple overlapping checks.
|
|
75
|
+
|
|
76
|
+
## Overview
|
|
77
|
+
|
|
78
|
+
When validating fields that have multiple possible error conditions (missing, empty, invalid enum
|
|
79
|
+
value), careless ordering can produce duplicate warnings for the same underlying issue. This
|
|
80
|
+
confuses users and makes debugging harder.
|
|
81
|
+
|
|
82
|
+
## Solution
|
|
83
|
+
|
|
84
|
+
1. **Order validation checks from most specific to most general** - check for missing/empty before
|
|
85
|
+
checking for invalid values
|
|
86
|
+
1. **Use else-if chains** to ensure only one warning per field
|
|
87
|
+
1. **Exclude empty/null values from enum validation** - an empty string is "empty," not "invalid enum"
|
|
88
|
+
1. **Write integration tests** that verify exactly one warning per field condition
|
|
89
|
+
1. **Use explicit presence checks** - avoid truthiness-based validation that conflates different issues
|
|
90
|
+
|
|
91
|
+
## Truthiness vs Presence Checks
|
|
92
|
+
|
|
93
|
+
### The Anti-Pattern: Using Truthiness for Presence
|
|
94
|
+
|
|
95
|
+
Truthiness-based checks (`!value`, `if (value)`) conflate multiple distinct conditions, producing
|
|
96
|
+
misleading error messages and duplicate warnings.
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
// WRONG: Truthiness-based validation
|
|
100
|
+
function validateField(frontmatter, field) {
|
|
101
|
+
if (!frontmatter[field]) {
|
|
102
|
+
// This triggers for:
|
|
103
|
+
// - undefined (missing)
|
|
104
|
+
// - null (missing)
|
|
105
|
+
// - "" (empty string - should be separate error)
|
|
106
|
+
// - 0 (legitimate value for numeric fields!)
|
|
107
|
+
// - false (legitimate value for boolean fields!)
|
|
108
|
+
errors.push(`Required field '${field}' is missing`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### The Correct Pattern: Explicit Presence Checks
|
|
114
|
+
|
|
115
|
+
Use explicit null/undefined checks, then separate empty string checks:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// RIGHT: Presence-based validation with explicit checks
|
|
119
|
+
function validateField(frontmatter, field) {
|
|
120
|
+
if (frontmatter[field] === undefined || frontmatter[field] === null) {
|
|
121
|
+
errors.push(`Required field '${field}' is missing`);
|
|
122
|
+
} else if (frontmatter[field] === "") {
|
|
123
|
+
errors.push(`Required field '${field}' is empty`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ALSO RIGHT: Using loose equality shorthand (null == undefined is true)
|
|
128
|
+
function validateField(frontmatter, field) {
|
|
129
|
+
if (frontmatter[field] == null) {
|
|
130
|
+
errors.push(`Required field '${field}' is missing`);
|
|
131
|
+
} else if (frontmatter[field] === "") {
|
|
132
|
+
errors.push(`Required field '${field}' is empty`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### The Guard Clause Pattern: `!= null && !== ''`
|
|
138
|
+
|
|
139
|
+
When validating a value (checking if it's in an enum, matches a regex, etc.), guard against
|
|
140
|
+
both missing and empty values to prevent spurious "invalid value" errors:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// RIGHT: Guard against missing and empty before validating
|
|
144
|
+
if (value != null && value !== "" && !VALID_VALUES.includes(value)) {
|
|
145
|
+
errors.push(`Invalid ${fieldName}: '${value}'`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// WRONG: Missing guard allows empty string to be flagged as "invalid"
|
|
149
|
+
if (!VALID_VALUES.includes(value)) {
|
|
150
|
+
// Empty string "" will produce: "Invalid fieldName: ''"
|
|
151
|
+
// Should be "fieldName is empty" instead!
|
|
152
|
+
errors.push(`Invalid ${fieldName}: '${value}'`);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Type Coercion Considerations
|
|
157
|
+
|
|
158
|
+
### When to Use `String()`
|
|
159
|
+
|
|
160
|
+
YAML parsers may return non-string types for values that look like numbers or dates:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
version: 1.0.0 # Parsed as number 1 (decimal truncated!)
|
|
164
|
+
created: 2026-01-30 # Parsed as Date object in some parsers
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use `String()` to safely coerce before string operations:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// RIGHT: Coerce before regex matching
|
|
171
|
+
if (frontmatter.version != null && frontmatter.version !== "") {
|
|
172
|
+
const versionStr = String(frontmatter.version);
|
|
173
|
+
if (!versionStr.match(/^\d+\.\d+\.\d+$/)) {
|
|
174
|
+
warnings.push(`Version '${versionStr}' should be semver format`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// WRONG: Assumes string type
|
|
179
|
+
if (frontmatter.version != null && frontmatter.version !== "") {
|
|
180
|
+
if (!frontmatter.version.match(/^\d+\.\d+\.\d+$/)) {
|
|
181
|
+
// TypeError if version is a number!
|
|
182
|
+
warnings.push(`Invalid version format`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Edge Cases with Type Coercion
|
|
188
|
+
|
|
189
|
+
| Input Value | `String(value)` Result | Notes |
|
|
190
|
+
| ----------- | ---------------------- | ----------------------------- |
|
|
191
|
+
| `1.0` | `"1"` | Decimal .0 is lost! |
|
|
192
|
+
| `null` | `"null"` | Check before coercing |
|
|
193
|
+
| `undefined` | `"undefined"` | Check before coercing |
|
|
194
|
+
| `[1, 2]` | `"1,2"` | Array becomes comma-separated |
|
|
195
|
+
| `{a: 1}` | `"[object Object]"` | Objects need special handling |
|
|
196
|
+
| `true` | `"true"` | Boolean to string |
|
|
197
|
+
| `0` | `"0"` | Zero is preserved |
|
|
198
|
+
|
|
199
|
+
**Best Practice**: Always check for `null`/empty before coercing:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
if (value != null && value !== "") {
|
|
203
|
+
const strValue = String(value);
|
|
204
|
+
// Now safe to validate strValue
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## The Duplicate Warning Problem
|
|
209
|
+
|
|
210
|
+
### Anti-Pattern: Overlapping Validation Checks
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
// BAD: Two separate if statements can both trigger for the same issue
|
|
214
|
+
function validateField(value, fieldName) {
|
|
215
|
+
const warnings = [];
|
|
216
|
+
|
|
217
|
+
// Check 1: Is the value invalid according to enum?
|
|
218
|
+
if (!VALID_VALUES.includes(value)) {
|
|
219
|
+
warnings.push(`Invalid ${fieldName}: '${value}'`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check 2: Is the value missing or empty?
|
|
223
|
+
if (value == null || value === "") {
|
|
224
|
+
warnings.push(`Missing ${fieldName}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return warnings;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// When called with empty string:
|
|
231
|
+
validateField("", "complexity.level");
|
|
232
|
+
// Returns TWO warnings:
|
|
233
|
+
// - "Invalid complexity.level: ''" (because '' is not in VALID_VALUES)
|
|
234
|
+
// - "Missing complexity.level" (because '' === "")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Correct Pattern: Mutually Exclusive Checks
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
// GOOD: Ordered checks ensure exactly one warning
|
|
241
|
+
function validateField(value, fieldName) {
|
|
242
|
+
const warnings = [];
|
|
243
|
+
|
|
244
|
+
// Check 1: Is the value missing?
|
|
245
|
+
if (value == null) {
|
|
246
|
+
warnings.push(`Missing ${fieldName}`);
|
|
247
|
+
}
|
|
248
|
+
// Check 2: Is the value empty string?
|
|
249
|
+
else if (value === "") {
|
|
250
|
+
warnings.push(`Empty ${fieldName}`);
|
|
251
|
+
}
|
|
252
|
+
// Check 3: Is the value invalid? (Only check if value is present and non-empty)
|
|
253
|
+
else if (!VALID_VALUES.includes(value)) {
|
|
254
|
+
warnings.push(`Invalid ${fieldName}: '${value}'`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return warnings;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// When called with empty string:
|
|
261
|
+
validateField("", "complexity.level");
|
|
262
|
+
// Returns exactly ONE warning:
|
|
263
|
+
// - "Empty complexity.level"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Real-World Example: Skill File Validation
|
|
267
|
+
|
|
268
|
+
This pattern is implemented in [validate-skills.js](../../scripts/validate-skills.js):
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
// Validate complexity level - exclude empty values from enum check
|
|
272
|
+
if (
|
|
273
|
+
frontmatter.complexity != null &&
|
|
274
|
+
frontmatter.complexity.level != null &&
|
|
275
|
+
frontmatter.complexity.level !== "" && // <-- Key: exclude empty strings
|
|
276
|
+
!VALID_COMPLEXITY_LEVELS.includes(frontmatter.complexity.level)
|
|
277
|
+
) {
|
|
278
|
+
warnings.push(
|
|
279
|
+
new ValidationError(
|
|
280
|
+
skillFile.relativePath,
|
|
281
|
+
"complexity.level",
|
|
282
|
+
`Invalid complexity level '${frontmatter.complexity.level}'. Valid: ${VALID_COMPLEXITY_LEVELS.join(", ")}`
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Later, check for missing/empty (mutually exclusive with above)
|
|
288
|
+
if (frontmatter.complexity == null || frontmatter.complexity.level == null) {
|
|
289
|
+
warnings.push(
|
|
290
|
+
new ValidationError(
|
|
291
|
+
skillFile.relativePath,
|
|
292
|
+
"complexity.level",
|
|
293
|
+
`Missing 'complexity.level' - will show '?' in Complexity column of skills index`
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
} else if (frontmatter.complexity.level === "") {
|
|
297
|
+
warnings.push(
|
|
298
|
+
new ValidationError(
|
|
299
|
+
skillFile.relativePath,
|
|
300
|
+
"complexity.level",
|
|
301
|
+
`Empty 'complexity.level' - will show '?' in Complexity column of skills index`
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Integration Testing for Warning Counts
|
|
308
|
+
|
|
309
|
+
Always write integration tests that verify the exact number of warnings produced:
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
// GOOD: Integration test that verifies no duplicate warnings
|
|
313
|
+
describe("empty string complexity.level produces exactly one warning", () => {
|
|
314
|
+
test("should produce exactly 1 warning (no duplicate from invalid enum check)", () => {
|
|
315
|
+
const content = createValidFrontmatter({
|
|
316
|
+
complexity: { level: "" }
|
|
317
|
+
});
|
|
318
|
+
const skillFile = createMockSkillFile(tempDir, content);
|
|
319
|
+
|
|
320
|
+
const result = validateSkill(skillFile);
|
|
321
|
+
|
|
322
|
+
// Filter warnings for the specific field
|
|
323
|
+
const complexityWarnings = result.warnings.filter((w) => w.field === "complexity.level");
|
|
324
|
+
|
|
325
|
+
// Key assertion: exactly ONE warning, not two
|
|
326
|
+
expect(complexityWarnings).toHaveLength(1);
|
|
327
|
+
expect(complexityWarnings[0].message).toContain("Empty 'complexity.level'");
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Validation Check Ordering Rules
|
|
333
|
+
|
|
334
|
+
1. **Null/undefined first** - the most fundamental check (is the value present?)
|
|
335
|
+
1. **Empty string second** - present but semantically empty
|
|
336
|
+
1. **Type checks third** - is it the right type (array, object, string)?
|
|
337
|
+
1. **Format/enum checks last** - only check value validity if value is present and correct type
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
// Optimal ordering for comprehensive validation
|
|
341
|
+
function validateOptionalField(value, fieldName, validValues) {
|
|
342
|
+
// 1. Missing check
|
|
343
|
+
if (value == null) {
|
|
344
|
+
return [{ type: "warning", message: `Missing ${fieldName}` }];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 2. Empty string check
|
|
348
|
+
if (value === "") {
|
|
349
|
+
return [{ type: "warning", message: `Empty ${fieldName}` }];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 3. Type check (if applicable)
|
|
353
|
+
if (typeof value !== "string") {
|
|
354
|
+
return [{ type: "warning", message: `${fieldName} must be a string` }];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 4. Enum/format check (only reached if all above pass)
|
|
358
|
+
if (!validValues.includes(value)) {
|
|
359
|
+
return [{ type: "warning", message: `Invalid ${fieldName}: '${value}'` }];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return []; // Valid
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Common Mistakes Checklist
|
|
367
|
+
|
|
368
|
+
Before committing validation code, verify:
|
|
369
|
+
|
|
370
|
+
- [ ] Empty strings don't trigger both "invalid enum" and "empty field" warnings
|
|
371
|
+
- [ ] Null/undefined values don't trigger both "invalid enum" and "missing field" warnings
|
|
372
|
+
- [ ] Integration tests verify exactly one warning per field per condition
|
|
373
|
+
- [ ] Validation checks use else-if chains or early returns to ensure mutual exclusivity
|
|
374
|
+
- [ ] Filter by field name when counting warnings in tests (to isolate the field under test)
|
|
375
|
+
|
|
376
|
+
## Troubleshooting
|
|
377
|
+
|
|
378
|
+
### Duplicate Warnings Appearing in Output
|
|
379
|
+
|
|
380
|
+
**Symptom**: A field produces two similar warnings (e.g., both "Missing X" and "Invalid X: ''")
|
|
381
|
+
|
|
382
|
+
**Diagnosis**:
|
|
383
|
+
|
|
384
|
+
1. Check if the enum validation excludes empty/null values with explicit checks
|
|
385
|
+
1. Verify else-if chains are used instead of separate if statements
|
|
386
|
+
1. Run integration tests that filter by field name and verify warning count
|
|
387
|
+
|
|
388
|
+
**Solution**: Ensure enum validation includes `value !== ''` in its guard clause:
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
// Add empty string exclusion to enum check
|
|
392
|
+
if (value != null && value !== "" && !VALID_VALUES.includes(value)) {
|
|
393
|
+
// Only one warning path
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Whitespace-Only Strings Triggering Wrong Warning
|
|
398
|
+
|
|
399
|
+
**Symptom**: Input like `" "` produces "Invalid enum" instead of "Empty field"
|
|
400
|
+
|
|
401
|
+
**Explanation**: Current validation does not trim whitespace, so `" "` is treated as a
|
|
402
|
+
non-empty string that fails enum validation. This is intentional - if whitespace-only should
|
|
403
|
+
be treated as empty, add explicit `.trim()` checks.
|
|
404
|
+
|
|
405
|
+
### Test Counting More Warnings Than Expected
|
|
406
|
+
|
|
407
|
+
**Symptom**: Test expects 1 warning but gets 2+
|
|
408
|
+
|
|
409
|
+
**Diagnosis**:
|
|
410
|
+
|
|
411
|
+
1. Filter warnings by field name: `result.warnings.filter(w => w.field === "fieldName")`
|
|
412
|
+
1. Log all warnings to see what's being produced
|
|
413
|
+
1. Check if both enum validation and missing/empty checks are triggering
|
|
414
|
+
|
|
415
|
+
## See Also
|
|
416
|
+
|
|
417
|
+
- [JavaScript Code Quality](javascript-code-quality.md) - General JavaScript best practices
|
|
418
|
+
- [Comprehensive Test Coverage](../testing/comprehensive-test-coverage.md) - Test coverage requirements
|
|
@@ -63,6 +63,9 @@ This document defines the structure, schema, and tooling for storing code patter
|
|
|
63
63
|
│ │ ├── powershell-best-practices.md
|
|
64
64
|
│ │ └── shell-patterns.md
|
|
65
65
|
│ │
|
|
66
|
+
│ ├── github-actions/ # GitHub Actions workflow patterns
|
|
67
|
+
│ │ └── workflow-consistency.md
|
|
68
|
+
│ │
|
|
66
69
|
│ └── documentation/ # Documentation and code comments
|
|
67
70
|
│ └── documentation-updates.md
|
|
68
71
|
```
|
|
@@ -78,7 +81,7 @@ Every skill file MUST include the following YAML frontmatter:
|
|
|
78
81
|
# Required Fields
|
|
79
82
|
title: "Human-readable skill title"
|
|
80
83
|
id: "unique-kebab-case-identifier"
|
|
81
|
-
category: "performance|testing|solid|messaging|unity|concurrency|architecture|error-handling|code-generation|documentation|scripting"
|
|
84
|
+
category: "performance|testing|solid|messaging|unity|concurrency|architecture|error-handling|code-generation|documentation|scripting|github-actions"
|
|
82
85
|
version: "1.0.0"
|
|
83
86
|
created: "2026-01-21"
|
|
84
87
|
updated: "2026-01-21"
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: test-code-quality
|
|
3
|
+
title: Test Code Quality and Accuracy
|
|
4
|
+
description: Best practices for test documentation accuracy, linter directive placement, and user-facing message consistency
|
|
5
|
+
category: testing
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
created: "2026-01-30"
|
|
8
|
+
updated: "2026-01-30"
|
|
9
|
+
status: stable
|
|
10
|
+
|
|
11
|
+
source:
|
|
12
|
+
repository: "wallstop/DxMessaging"
|
|
13
|
+
files:
|
|
14
|
+
- path: "scripts/__tests__/validate-skills-tags.test.js"
|
|
15
|
+
- path: "scripts/__tests__/validate-skills-optional-fields.test.js"
|
|
16
|
+
url: "https://github.com/wallstop/DxMessaging"
|
|
17
|
+
|
|
18
|
+
tags:
|
|
19
|
+
- testing
|
|
20
|
+
- documentation
|
|
21
|
+
- linting
|
|
22
|
+
- code-quality
|
|
23
|
+
- javascript
|
|
24
|
+
related:
|
|
25
|
+
- comprehensive-test-coverage
|
|
26
|
+
- script-test-coverage
|
|
27
|
+
complexity:
|
|
28
|
+
level: intermediate
|
|
29
|
+
reasoning: "Requires understanding of JavaScript truthiness and test organization principles"
|
|
30
|
+
impact:
|
|
31
|
+
performance:
|
|
32
|
+
rating: medium
|
|
33
|
+
description: Prevents misleading documentation and ensures test accuracy
|
|
34
|
+
maintainability:
|
|
35
|
+
rating: high
|
|
36
|
+
description: "Accurate test documentation improves codebase understanding"
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
# Test Code Quality and Accuracy
|
|
40
|
+
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
This skill covers best practices for writing accurate test documentation, proper linter directive placement, and ensuring consistency between user-facing messages and actual UI/output.
|
|
44
|
+
|
|
45
|
+
## Solution
|
|
46
|
+
|
|
47
|
+
## Linter Directive Placement
|
|
48
|
+
|
|
49
|
+
### The Problem
|
|
50
|
+
|
|
51
|
+
ESLint disable directives must be placed correctly to work:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// WRONG: Directive is NOT immediately before the line it suppresses
|
|
55
|
+
// eslint-disable-next-line unicorn/no-new-array
|
|
56
|
+
const frontmatter = {
|
|
57
|
+
title: "Sample",
|
|
58
|
+
tags: new Array("testing") // This line is NOT suppressed!
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// CORRECT: Directive is immediately before the target line
|
|
62
|
+
const frontmatter = {
|
|
63
|
+
title: "Sample",
|
|
64
|
+
// eslint-disable-next-line unicorn/no-new-array
|
|
65
|
+
tags: new Array("testing") // This line IS suppressed
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Best Practices
|
|
70
|
+
|
|
71
|
+
1. **`eslint-disable-next-line` suppresses exactly the next line** - not code several lines later
|
|
72
|
+
1. **Verify the linter is actually configured** - don't add directives for linters that aren't used
|
|
73
|
+
1. **Check `package.json` and config files** - look for `.eslintrc*`, `eslint.config.*`, or `eslint` in `devDependencies`
|
|
74
|
+
1. **Remove unnecessary directives** - if no linter is configured, the directive is dead code
|
|
75
|
+
|
|
76
|
+
## Test Documentation Accuracy
|
|
77
|
+
|
|
78
|
+
### The Problem
|
|
79
|
+
|
|
80
|
+
Test describe blocks and file headers must accurately describe what tests they contain.
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// WRONG: Header says "falsy/undefined" but tests wrong-type values
|
|
84
|
+
/**
|
|
85
|
+
* These tests validate:
|
|
86
|
+
* - Missing tags (falsy/undefined) // Inaccurate!
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
describe("missing tags field", () => {
|
|
90
|
+
test("should warn when tags is empty string", () => {
|
|
91
|
+
// WRONG PLACE
|
|
92
|
+
// Empty string is NOT "missing" - it's a wrong type
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// CORRECT: Accurate categorization
|
|
97
|
+
/**
|
|
98
|
+
* These tests validate:
|
|
99
|
+
* - Missing tags (undefined/null)
|
|
100
|
+
* - Wrong type for tags (string, number, boolean)
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
describe("missing tags field", () => {
|
|
104
|
+
test("should warn when tags is undefined", () => {
|
|
105
|
+
/* ... */
|
|
106
|
+
});
|
|
107
|
+
test("should warn when tags is null", () => {
|
|
108
|
+
/* ... */
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("wrong type for tags", () => {
|
|
113
|
+
test("should warn when tags is an empty string", () => {
|
|
114
|
+
/* ... */
|
|
115
|
+
});
|
|
116
|
+
test("should warn when tags is a zero", () => {
|
|
117
|
+
/* ... */
|
|
118
|
+
});
|
|
119
|
+
test("should warn when tags is boolean false", () => {
|
|
120
|
+
/* ... */
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### JavaScript Truthiness Reference
|
|
126
|
+
|
|
127
|
+
Understanding JavaScript's truthy/falsy values prevents misclassification:
|
|
128
|
+
|
|
129
|
+
| Value | Truthy/Falsy | Category | Correct Test Location |
|
|
130
|
+
| ------------------- | ------------ | -------------------- | ------------------------ |
|
|
131
|
+
| `undefined` | Falsy | Missing | "missing field" describe |
|
|
132
|
+
| `null` | Falsy | Missing | "missing field" describe |
|
|
133
|
+
| `""` (empty string) | Falsy | Wrong type | "wrong type" describe |
|
|
134
|
+
| `0` | Falsy | Wrong type | "wrong type" describe |
|
|
135
|
+
| `false` | Falsy | Wrong type | "wrong type" describe |
|
|
136
|
+
| `[]` (empty array) | **Truthy** | Correct type (empty) | "empty array" describe |
|
|
137
|
+
| `{}` (empty object) | **Truthy** | Wrong type | "wrong type" describe |
|
|
138
|
+
|
|
139
|
+
### Key Distinction
|
|
140
|
+
|
|
141
|
+
- **Missing**: Value is `undefined` or `null` - the field doesn't exist or is explicitly null
|
|
142
|
+
- **Wrong type**: Value exists but is the wrong JavaScript type (string instead of array, etc.)
|
|
143
|
+
- **Empty**: Value exists with correct type but has no content (empty array, empty string)
|
|
144
|
+
|
|
145
|
+
## User-Facing Message Consistency
|
|
146
|
+
|
|
147
|
+
### The Problem
|
|
148
|
+
|
|
149
|
+
Warning messages and error messages displayed to users must match actual UI/output terminology.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// WRONG: Message says "Difficulty column" but actual table header is "Complexity"
|
|
153
|
+
warnings.push(`Missing 'complexity.level' - will show '?' in Difficulty column`);
|
|
154
|
+
|
|
155
|
+
// CORRECT: Message matches actual column name
|
|
156
|
+
warnings.push(`Missing 'complexity.level' - will show '?' in Complexity column`);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Best Practices
|
|
160
|
+
|
|
161
|
+
1. **Verify against source of truth** - read the actual output generation code to confirm terminology
|
|
162
|
+
1. **Use grep/search to find actual column names** - e.g., search for table header generation
|
|
163
|
+
1. **Keep messages synchronized** - when UI changes, update all related messages
|
|
164
|
+
1. **Add SYNC notes** - reference the source of truth in comments
|
|
165
|
+
|
|
166
|
+
Example:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// SYNC: Column names must match table headers in generate-skills-index.js
|
|
170
|
+
// Actual headers: | Skill | Lines | Complexity | Status | Performance | Tags |
|
|
171
|
+
`Missing 'complexity.level' - will show '?' in Complexity column of skills index`;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Test Coverage for Message Content
|
|
175
|
+
|
|
176
|
+
### The Problem
|
|
177
|
+
|
|
178
|
+
When user-facing messages are changed, tests should verify the message content is correct.
|
|
179
|
+
|
|
180
|
+
### Best Practice
|
|
181
|
+
|
|
182
|
+
Add tests that check message content matches actual terminology:
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
describe("warning message content", () => {
|
|
186
|
+
test("should reference correct Complexity column name", () => {
|
|
187
|
+
const warnings = validateComplexityLevel(frontmatter, testPath);
|
|
188
|
+
|
|
189
|
+
expect(warnings[0].message).toContain("Complexity column");
|
|
190
|
+
// Ensures we don't accidentally say "Difficulty column"
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("should reference correct Performance column name", () => {
|
|
194
|
+
const warnings = validatePerformanceRating(frontmatter, testPath);
|
|
195
|
+
|
|
196
|
+
expect(warnings[0].message).toContain("Performance column");
|
|
197
|
+
// Ensures we don't accidentally say "Priority column"
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Bidirectional SYNC Notes
|
|
203
|
+
|
|
204
|
+
### The Problem
|
|
205
|
+
|
|
206
|
+
SYNC notes only work when they exist in both directions.
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
// FILE A: validate-skills.js
|
|
210
|
+
// SYNC: Keep logic in sync with validate-skills-tags.test.js validateTags()
|
|
211
|
+
if (frontmatter.tags === undefined || frontmatter.tags === null) {
|
|
212
|
+
|
|
213
|
+
// FILE B: validate-skills-tags.test.js
|
|
214
|
+
/**
|
|
215
|
+
* SYNC: Keep logic in sync with validate-skills.js validateSkill() tags validation block
|
|
216
|
+
*/
|
|
217
|
+
function validateTags(frontmatter, relativePath) {
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Requirements
|
|
221
|
+
|
|
222
|
+
1. **Always bidirectional** - when A references B, B must reference A
|
|
223
|
+
1. **Reference function/block names** - not line numbers (which become stale)
|
|
224
|
+
1. **Be specific** - identify the exact function or logical block
|
|
225
|
+
1. **Verify references exist** - confirm the target exists before adding a SYNC note
|
|
226
|
+
|
|
227
|
+
## Pre-Commit Checklist
|
|
228
|
+
|
|
229
|
+
Before committing test code, verify:
|
|
230
|
+
|
|
231
|
+
- [ ] Linter directives are immediately before the line they suppress
|
|
232
|
+
- [ ] Linter is actually configured (check `package.json`, `.eslintrc*`, etc.)
|
|
233
|
+
- [ ] `describe()` block names accurately reflect the tests they contain
|
|
234
|
+
- [ ] File header comments accurately describe test categories
|
|
235
|
+
- [ ] "Missing" vs "wrong type" vs "empty" tests are correctly categorized
|
|
236
|
+
- [ ] User-facing messages match actual UI/output terminology
|
|
237
|
+
- [ ] Message content is tested if it references specific column names or labels
|
|
238
|
+
- [ ] SYNC notes exist in both directions
|
|
239
|
+
|
|
240
|
+
## See Also
|
|
241
|
+
|
|
242
|
+
- [Comprehensive Test Coverage](comprehensive-test-coverage.md)
|
|
243
|
+
- [Script Test Coverage](script-test-coverage.md)
|