com.wallstop-studios.dxmessaging 2.1.5 → 2.1.7
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 +24 -0
- package/README.md +113 -24
- 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 +7 -2
- 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/scripts/wiki/generate-wiki-sidebar.js.meta +1 -8
- package/scripts/wiki/transform-docs-to-wiki.js.meta +1 -1
- 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,348 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Test Production Code Directly"
|
|
3
|
+
id: "test-production-code"
|
|
4
|
+
category: "testing"
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
created: "2026-01-30"
|
|
7
|
+
updated: "2026-01-30"
|
|
8
|
+
|
|
9
|
+
source:
|
|
10
|
+
repository: "wallstop/DxMessaging"
|
|
11
|
+
files:
|
|
12
|
+
- path: "scripts/__tests__/"
|
|
13
|
+
- path: "scripts/"
|
|
14
|
+
url: "https://github.com/wallstop/DxMessaging"
|
|
15
|
+
|
|
16
|
+
tags:
|
|
17
|
+
- "testing"
|
|
18
|
+
- "anti-patterns"
|
|
19
|
+
- "code-quality"
|
|
20
|
+
- "javascript"
|
|
21
|
+
- "maintainability"
|
|
22
|
+
- "testability"
|
|
23
|
+
- "best-practices"
|
|
24
|
+
|
|
25
|
+
complexity:
|
|
26
|
+
level: "intermediate"
|
|
27
|
+
reasoning: "Requires understanding of test design principles and code architecture"
|
|
28
|
+
|
|
29
|
+
impact:
|
|
30
|
+
performance:
|
|
31
|
+
rating: "none"
|
|
32
|
+
details: "Testing patterns only; no runtime performance impact"
|
|
33
|
+
maintainability:
|
|
34
|
+
rating: "critical"
|
|
35
|
+
details: "Prevents tests from diverging from production behavior"
|
|
36
|
+
testability:
|
|
37
|
+
rating: "critical"
|
|
38
|
+
details: "Ensures tests actually verify production code correctness"
|
|
39
|
+
|
|
40
|
+
prerequisites:
|
|
41
|
+
- "Understanding of module exports and imports"
|
|
42
|
+
- "Familiarity with test isolation principles"
|
|
43
|
+
|
|
44
|
+
dependencies:
|
|
45
|
+
packages: []
|
|
46
|
+
skills:
|
|
47
|
+
- "comprehensive-test-coverage"
|
|
48
|
+
- "script-test-coverage"
|
|
49
|
+
|
|
50
|
+
applies_to:
|
|
51
|
+
languages:
|
|
52
|
+
- "JavaScript"
|
|
53
|
+
- "TypeScript"
|
|
54
|
+
- "C#"
|
|
55
|
+
frameworks:
|
|
56
|
+
- "Jest"
|
|
57
|
+
- "Node.js"
|
|
58
|
+
- "NUnit"
|
|
59
|
+
versions:
|
|
60
|
+
node: ">=18.0"
|
|
61
|
+
jest: ">=29.0"
|
|
62
|
+
|
|
63
|
+
aliases:
|
|
64
|
+
- "Test real code"
|
|
65
|
+
- "Don't re-implement in tests"
|
|
66
|
+
- "Test production directly"
|
|
67
|
+
- "Avoid test duplication"
|
|
68
|
+
|
|
69
|
+
related:
|
|
70
|
+
- "comprehensive-test-coverage"
|
|
71
|
+
- "script-test-coverage"
|
|
72
|
+
- "test-code-quality"
|
|
73
|
+
|
|
74
|
+
status: "stable"
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# Test Production Code Directly
|
|
78
|
+
|
|
79
|
+
> **One-line summary**: Tests must import and use production code, never re-implement production logic locally.
|
|
80
|
+
|
|
81
|
+
## Overview
|
|
82
|
+
|
|
83
|
+
A common anti-pattern in testing is re-implementing production validation logic inside test files. When tests maintain their own copies of validation rules, they can pass even when production code regresses. This skill documents how to structure code for testability and avoid this dangerous pattern.
|
|
84
|
+
|
|
85
|
+
## Problem Statement
|
|
86
|
+
|
|
87
|
+
### The Anti-Pattern
|
|
88
|
+
|
|
89
|
+
Tests that re-implement production logic create a false sense of security:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// PRODUCTION: scripts/validate-data.js
|
|
93
|
+
function validateRecord(record) {
|
|
94
|
+
const errors = [];
|
|
95
|
+
if (!record.name || record.name.length < 3) {
|
|
96
|
+
errors.push("Name must be at least 3 characters");
|
|
97
|
+
}
|
|
98
|
+
if (!record.email || !record.email.includes("@")) {
|
|
99
|
+
errors.push("Email must be valid");
|
|
100
|
+
}
|
|
101
|
+
return errors;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// TEST: scripts/__tests__/validate-data.test.js
|
|
105
|
+
// WRONG: Re-implementing validation locally
|
|
106
|
+
function localValidateRecord(record) {
|
|
107
|
+
const errors = [];
|
|
108
|
+
if (!record.name || record.name.length < 3) {
|
|
109
|
+
// Duplicated logic!
|
|
110
|
+
errors.push("Name must be at least 3 characters");
|
|
111
|
+
}
|
|
112
|
+
if (!record.email || !record.email.includes("@")) {
|
|
113
|
+
// Duplicated logic!
|
|
114
|
+
errors.push("Email must be valid");
|
|
115
|
+
}
|
|
116
|
+
return errors;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
test("should validate record correctly", () => {
|
|
120
|
+
const result = localValidateRecord({ name: "Jo", email: "bad" });
|
|
121
|
+
expect(result).toHaveLength(2); // Tests pass, but production untested!
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Why This Is Dangerous
|
|
126
|
+
|
|
127
|
+
1. **Production regressions go undetected**: If someone changes `< 3` to `< 2` in production, tests still pass because they use the local copy.
|
|
128
|
+
1. **Maintenance burden doubles**: Every production change requires updating test copies.
|
|
129
|
+
1. **Divergence over time**: Local test copies gradually drift from production reality.
|
|
130
|
+
1. **False confidence**: 100% test coverage means nothing if tests don't exercise production code.
|
|
131
|
+
1. **Bug duplication**: If the same bug exists in both copies, tests won't catch it.
|
|
132
|
+
|
|
133
|
+
## Solution
|
|
134
|
+
|
|
135
|
+
### Structure Production Code for Testability
|
|
136
|
+
|
|
137
|
+
Export validation functions and helper logic so tests can import them:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// PRODUCTION: scripts/validate-data.js
|
|
141
|
+
/**
|
|
142
|
+
* Validates a record and returns any errors.
|
|
143
|
+
* @param {Object} record - The record to validate
|
|
144
|
+
* @returns {string[]} Array of error messages
|
|
145
|
+
*/
|
|
146
|
+
function validateRecord(record) {
|
|
147
|
+
const errors = [];
|
|
148
|
+
if (!isValidName(record.name)) {
|
|
149
|
+
errors.push("Name must be at least 3 characters");
|
|
150
|
+
}
|
|
151
|
+
if (!isValidEmail(record.email)) {
|
|
152
|
+
errors.push("Email must be valid");
|
|
153
|
+
}
|
|
154
|
+
return errors;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Validates name length.
|
|
159
|
+
* @param {string} name - The name to validate
|
|
160
|
+
* @returns {boolean} True if valid
|
|
161
|
+
*/
|
|
162
|
+
function isValidName(name) {
|
|
163
|
+
return name && name.length >= 3;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validates email format.
|
|
168
|
+
* @param {string} email - The email to validate
|
|
169
|
+
* @returns {boolean} True if valid
|
|
170
|
+
*/
|
|
171
|
+
function isValidEmail(email) {
|
|
172
|
+
return email && email.includes("@");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Export everything tests need
|
|
176
|
+
module.exports = {
|
|
177
|
+
validateRecord,
|
|
178
|
+
isValidName,
|
|
179
|
+
isValidEmail
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Test Production Code Directly
|
|
184
|
+
|
|
185
|
+
Import and test the actual production functions:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// TEST: scripts/__tests__/validate-data.test.js
|
|
189
|
+
// CORRECT: Import production code
|
|
190
|
+
const { validateRecord, isValidName, isValidEmail } = require("../validate-data");
|
|
191
|
+
|
|
192
|
+
describe("validateRecord", () => {
|
|
193
|
+
test("should return no errors for valid record", () => {
|
|
194
|
+
const result = validateRecord({ name: "John", email: "john@example.com" });
|
|
195
|
+
expect(result).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("should return error for short name", () => {
|
|
199
|
+
const result = validateRecord({ name: "Jo", email: "john@example.com" });
|
|
200
|
+
expect(result).toContain("Name must be at least 3 characters");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("should return error for invalid email", () => {
|
|
204
|
+
const result = validateRecord({ name: "John", email: "invalid" });
|
|
205
|
+
expect(result).toContain("Email must be valid");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("isValidName", () => {
|
|
210
|
+
test("should accept names with 3 or more characters", () => {
|
|
211
|
+
expect(isValidName("Joe")).toBe(true);
|
|
212
|
+
expect(isValidName("John")).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("should reject names with fewer than 3 characters", () => {
|
|
216
|
+
expect(isValidName("Jo")).toBe(false);
|
|
217
|
+
expect(isValidName("J")).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("should reject null and undefined", () => {
|
|
221
|
+
expect(isValidName(null)).toBe(false);
|
|
222
|
+
expect(isValidName(undefined)).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## When Local Test Helpers Are Acceptable
|
|
228
|
+
|
|
229
|
+
Not all code in test files is duplication. These are legitimate uses:
|
|
230
|
+
|
|
231
|
+
### Thin Wrappers for Test Convenience
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// ACCEPTABLE: Thin wrapper that delegates to production
|
|
235
|
+
function validateAndExpectErrors(record, expectedCount) {
|
|
236
|
+
const result = validateRecord(record); // Calls production!
|
|
237
|
+
expect(result).toHaveLength(expectedCount);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Test-Only Utilities
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// ACCEPTABLE: Test data generators
|
|
246
|
+
function createValidRecord(overrides = {}) {
|
|
247
|
+
return {
|
|
248
|
+
name: "Default Name",
|
|
249
|
+
email: "default@example.com",
|
|
250
|
+
...overrides
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ACCEPTABLE: Custom matchers
|
|
255
|
+
function expectValidationError(result, expectedMessage) {
|
|
256
|
+
expect(result.some((msg) => msg.includes(expectedMessage))).toBe(true);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ACCEPTABLE: Factories for test data
|
|
260
|
+
const TestRecords = {
|
|
261
|
+
valid: { name: "John Doe", email: "john@example.com" },
|
|
262
|
+
invalidName: { name: "X", email: "john@example.com" }
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Handling Unavoidable Duplication
|
|
267
|
+
|
|
268
|
+
Some situations require maintaining parallel implementations. Use SYNC notes to keep them aligned.
|
|
269
|
+
|
|
270
|
+
### PowerShell Scripts with JavaScript Tests
|
|
271
|
+
|
|
272
|
+
When the production script is PowerShell but tests are JavaScript:
|
|
273
|
+
|
|
274
|
+
```powershell
|
|
275
|
+
# PRODUCTION: scripts/validate-data.ps1
|
|
276
|
+
# SYNC: Keep validation logic in sync with validate-data.test.js validateRecord()
|
|
277
|
+
function Test-Record {
|
|
278
|
+
param([hashtable]$Record)
|
|
279
|
+
$errors = @()
|
|
280
|
+
if (-not $Record.name -or $Record.name.Length -lt 3) {
|
|
281
|
+
$errors += "Name must be at least 3 characters"
|
|
282
|
+
}
|
|
283
|
+
return $errors
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
// TEST: scripts/__tests__/validate-data.test.js
|
|
289
|
+
// SYNC: Keep validation logic in sync with validate-data.ps1 Test-Record
|
|
290
|
+
function validateRecord(record) {
|
|
291
|
+
const errors = [];
|
|
292
|
+
if (!record.name || record.name.length < 3) {
|
|
293
|
+
errors += "Name must be at least 3 characters";
|
|
294
|
+
}
|
|
295
|
+
return errors;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Browser-Only Code
|
|
300
|
+
|
|
301
|
+
When code runs only in browsers and cannot be tested in Node.js:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// PRODUCTION: browser-only.js (uses DOM APIs)
|
|
305
|
+
// SYNC: Core logic duplicated for testing in browser-only.test.js
|
|
306
|
+
|
|
307
|
+
// TEST: browser-only.test.js
|
|
308
|
+
// SYNC: Keep validation logic in sync with browser-only.js computeLayout()
|
|
309
|
+
// This is a test-only implementation because production uses DOM APIs
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## SYNC Note Best Practices
|
|
313
|
+
|
|
314
|
+
When duplication is unavoidable, follow these SYNC note guidelines:
|
|
315
|
+
|
|
316
|
+
1. **Always bidirectional**: Both files must reference each other
|
|
317
|
+
1. **Never use line numbers**: Reference function names, not line numbers that change
|
|
318
|
+
1. **Use descriptive identifiers**: `SYNC: Keep in sync with validate.js isValidEmail()` not `SYNC: Keep in sync with validate.js line 42`
|
|
319
|
+
1. **Verify references exist**: Confirm the referenced function exists before adding the note
|
|
320
|
+
1. **Update both on changes**: When modifying synced code, update both locations
|
|
321
|
+
|
|
322
|
+
## Red Flags to Watch For
|
|
323
|
+
|
|
324
|
+
These patterns suggest tests may not be exercising production code:
|
|
325
|
+
|
|
326
|
+
| Red Flag | Why It's Suspicious |
|
|
327
|
+
| ------------------------------------------------------ | ----------------------------- |
|
|
328
|
+
| Test file defines validation constants | Should import from production |
|
|
329
|
+
| Test file has utility functions that mirror production | Should import instead |
|
|
330
|
+
| Test never imports the module it's supposedly testing | Tests itself, not production |
|
|
331
|
+
| Test file size rivals production file size | Too much duplicated logic |
|
|
332
|
+
| Same bug exists in production and tests | Copied code with copied bugs |
|
|
333
|
+
|
|
334
|
+
## Verification Checklist
|
|
335
|
+
|
|
336
|
+
Before merging, verify tests exercise production code:
|
|
337
|
+
|
|
338
|
+
1. **Check imports**: Does the test file import from the production module?
|
|
339
|
+
1. **Check coverage**: Does production file show coverage when tests run?
|
|
340
|
+
1. **Mutation test**: Change production code slightly - do tests fail?
|
|
341
|
+
1. **Review test functions**: Are they calling production code or local copies?
|
|
342
|
+
1. **Search for duplication**: Do test and production files have similar function bodies?
|
|
343
|
+
|
|
344
|
+
## See Also
|
|
345
|
+
|
|
346
|
+
- [Comprehensive Test Coverage skill](comprehensive-test-coverage.md) - What to test
|
|
347
|
+
- [Script Test Coverage skill](script-test-coverage.md) - Testing scripts specifically
|
|
348
|
+
- [Test Code Quality skill](test-code-quality.md) - Test documentation accuracy
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.7]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Improved README with prominent Mental Model section
|
|
15
|
+
- Added Mermaid diagrams and decision flowchart for choosing message types
|
|
16
|
+
- Added Common Mistakes callout with troubleshooting link
|
|
17
|
+
- Updated performance comparison table with accurate benchmark range (10-17M ops/sec)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Regenerated corrupted meta files in `scripts/wiki`
|
|
22
|
+
|
|
23
|
+
## [2.1.6]
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Concepts index page and Mental Model documentation for understanding DxMessaging's design principles
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Orphaned documentation pages in Concepts section now included in mkdocs.yml navigation
|
|
32
|
+
- Burst compiler assembly resolution errors when using DxMessaging as a package on disk and building for player platforms. Benchmarks and integration test assembly definitions now specify Editor-only platform to prevent Burst from attempting to resolve these assemblies during player builds.
|
|
33
|
+
|
|
10
34
|
## [2.1.5]
|
|
11
35
|
|
|
12
36
|
### Added
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DxMessaging for Unity
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="docs/images/DxMessaging-banner.svg" alt="DxMessaging
|
|
4
|
+
<img src="docs/images/DxMessaging-banner.svg" alt="DxMessaging - Type-safe messaging system for Unity" width="800"/>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -31,9 +31,11 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
|
|
|
31
31
|
## Table of Contents
|
|
32
32
|
|
|
33
33
|
- [30-Second Elevator Pitch](#30-second-elevator-pitch)
|
|
34
|
+
- [Mental Model: How to Think About DxMessaging](#mental-model-how-to-think-about-dxmessaging)
|
|
34
35
|
- [Quick Start (5 Minutes)](#quick-start-5-minutes)
|
|
36
|
+
- [Dependency Injection (DI) Compatible](#-dependency-injection-di-compatible)
|
|
35
37
|
- [Is DxMessaging Right for You?](#is-dxmessaging-right-for-you)
|
|
36
|
-
- [Why DxMessaging
|
|
38
|
+
- [Why DxMessaging](#why-dxmessaging)
|
|
37
39
|
- [Key Features](#key-features)
|
|
38
40
|
- [The DxMessaging Solution](#the-dxmessaging-solution)
|
|
39
41
|
- [Real-World Examples](#real-world-examples)
|
|
@@ -65,14 +67,114 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
|
|
|
65
67
|
|
|
66
68
|
##### Three simple message types
|
|
67
69
|
|
|
68
|
-
1. **Untargeted** -
|
|
69
|
-
1. **Targeted** -
|
|
70
|
-
1. **Broadcast** -
|
|
70
|
+
1. **Untargeted** - Global announcements
|
|
71
|
+
1. **Targeted** - Commands to specific entities
|
|
72
|
+
1. **Broadcast** - Observable facts from a source
|
|
73
|
+
|
|
74
|
+
See [Mental Model](#mental-model-how-to-think-about-dxmessaging) for how to choose the right type.
|
|
71
75
|
|
|
72
76
|
**One line:** It's a type-safe messaging system with automatic lifecycle management and built-in inspection tools.
|
|
73
77
|
|
|
74
78
|
---
|
|
75
79
|
|
|
80
|
+
## Mental Model: How to Think About DxMessaging
|
|
81
|
+
|
|
82
|
+
### The Core Idea
|
|
83
|
+
|
|
84
|
+
DxMessaging is built around one principle: **it gets out of your way**.
|
|
85
|
+
|
|
86
|
+
You have data. You need to pass it around. That's the problem. DxMessaging provides fast, simple primitives as building blocks. You model changes as message types with optional context, using game primitives (GameObjects, components) as that context.
|
|
87
|
+
|
|
88
|
+
**You don't build your game INTO the messaging system.** It's opt-in and optional—a tool you reach for when it helps.
|
|
89
|
+
|
|
90
|
+
### The Three Message Types: Real-World Analogies
|
|
91
|
+
|
|
92
|
+
> 💡 _Diagrams below require Mermaid support. If they don't render, try viewing this file directly on [GitHub](https://github.com/wallstop/DxMessaging)._
|
|
93
|
+
|
|
94
|
+
Each message type maps to a real-world communication pattern:
|
|
95
|
+
|
|
96
|
+
#### 1. Untargeted = PA System 📢
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
flowchart LR
|
|
100
|
+
S[Someone] -->|announces| PA[📢 PA System]
|
|
101
|
+
PA --> L1[Listener A]
|
|
102
|
+
PA --> L2[Listener B]
|
|
103
|
+
PA --> L3[Listener C]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Announcements with no specific recipient. Everyone who cares can hear it.
|
|
107
|
+
|
|
108
|
+
**Examples:** "The game is paused", "Settings changed", "Scene finished loading"
|
|
109
|
+
|
|
110
|
+
#### 2. Targeted = Addressed Letter 📬
|
|
111
|
+
|
|
112
|
+
```mermaid
|
|
113
|
+
flowchart LR
|
|
114
|
+
S[Sender] -->|"To: Player"| Letter[📬 Message Bus]
|
|
115
|
+
Letter --> Player[Player receives]
|
|
116
|
+
Other1[Enemy A] -.->|ignores| Letter
|
|
117
|
+
Other2[Enemy B] -.->|ignores| Letter
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Commands to a specific recipient. Only that entity receives them.
|
|
121
|
+
|
|
122
|
+
**Examples:** "Player, heal for 10 HP", "Door #7, open", "This enemy, take 25 damage"
|
|
123
|
+
|
|
124
|
+
#### 3. Broadcast = Radio Station 📻
|
|
125
|
+
|
|
126
|
+
```mermaid
|
|
127
|
+
flowchart LR
|
|
128
|
+
Source[Enemy] -->|"I took damage!"| Radio[📻 Message Bus]
|
|
129
|
+
Radio --> L1[Damage Numbers UI]
|
|
130
|
+
Radio --> L2[Achievement Tracker]
|
|
131
|
+
Radio --> L3[Analytics]
|
|
132
|
+
Radio --> L4[Combat Log]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Facts emitted by a specific source. No intended recipient—just an origin. Anyone can tune in.
|
|
136
|
+
|
|
137
|
+
**Examples:** "This enemy took 25 damage", "The player picked up item X", "This chest opened"
|
|
138
|
+
|
|
139
|
+
### Choosing the Right Message Type
|
|
140
|
+
|
|
141
|
+
```mermaid
|
|
142
|
+
flowchart TD
|
|
143
|
+
Start([I need to send a message])
|
|
144
|
+
Start --> Q1{Does it matter<br/>WHO sent it?}
|
|
145
|
+
|
|
146
|
+
Q1 -->|No| Q2{Does it matter<br/>WHO receives it?}
|
|
147
|
+
Q2 -->|No| Untargeted[Use UNTARGETED<br/>Global announcement]
|
|
148
|
+
Q2 -->|Yes| Targeted[Use TARGETED<br/>Directed command]
|
|
149
|
+
|
|
150
|
+
Q1 -->|Yes| Q3{Am I commanding<br/>someone to act?}
|
|
151
|
+
Q3 -->|Yes| Targeted
|
|
152
|
+
Q3 -->|No| Broadcast[Use BROADCAST<br/>Observable fact]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Question | Untargeted | Targeted | Broadcast |
|
|
156
|
+
| ----------------------------------- | :--------: | :------: | :-------: |
|
|
157
|
+
| Has a specific sender that matters? | ❌ | ❌ | ✅ |
|
|
158
|
+
| Has a specific recipient? | ❌ | ✅ | ❌ |
|
|
159
|
+
| Is it a command? | ❌ | ✅ | ❌ |
|
|
160
|
+
| Is it an observable fact? | Maybe | ❌ | ✅ |
|
|
161
|
+
| Is it a global announcement? | ✅ | ❌ | ❌ |
|
|
162
|
+
|
|
163
|
+
> ⚠️ **Common Mistakes:**
|
|
164
|
+
>
|
|
165
|
+
> - **Forgetting to enable the token** — Messages won't be received. Use `MessageAwareComponent` (auto-enables) or call `Token.Enable()` manually.
|
|
166
|
+
> - **Targeting Component when you meant GameObject** — These are distinct registration paths. Component-targeted messages won't reach GameObject-level handlers.
|
|
167
|
+
> - **Using Broadcast when you need Targeted** — Broadcasts have no recipient, just an origin. Use Targeted when commanding a specific entity.
|
|
168
|
+
> - **Missing `[Dx*Message]` attribute** — The source generator won't process the struct without the marker attribute.
|
|
169
|
+
>
|
|
170
|
+
> 📖 See [Troubleshooting](docs/reference/troubleshooting.md) for solutions to these and other issues.
|
|
171
|
+
|
|
172
|
+
📖 **Want more depth?** See the full [Mental Model documentation](docs/concepts/mental-model.md) for detailed examples, lifecycle patterns, and edge cases.
|
|
173
|
+
|
|
174
|
+
📖 **Ready to code?** Jump to [Quick Start](#quick-start-5-minutes) to send your first message!
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
76
178
|
## Quick Start (5 Minutes)
|
|
77
179
|
|
|
78
180
|
**New to messaging?** Start with the [Visual Guide](docs/getting-started/visual-guide.md) (5 min) for a beginner-friendly introduction!
|
|
@@ -85,7 +187,7 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
|
|
|
85
187
|
openupm add com.wallstop-studios.dxmessaging
|
|
86
188
|
```
|
|
87
189
|
|
|
88
|
-
|
|
190
|
+
#### Or via Git URL
|
|
89
191
|
|
|
90
192
|
```bash
|
|
91
193
|
# Unity Package Manager > Add package from git URL...
|
|
@@ -231,20 +333,10 @@ flowchart TD
|
|
|
231
333
|
Q3{Do you need observable, decoupled,<br/>lifecycle-safe messaging?}
|
|
232
334
|
Q3 -->|YES| A3["✅ Use DxMessaging"]
|
|
233
335
|
Q3 -->|NO| A4["❌ Keep it simple"]
|
|
234
|
-
|
|
235
|
-
style Q1 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
|
|
236
|
-
style Q2 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
|
|
237
|
-
style Q3 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
|
|
238
|
-
style A1 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
|
|
239
|
-
style A2 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
|
|
240
|
-
style A3 fill:#95de64,stroke:#237804,stroke-width:3px,color:#000
|
|
241
|
-
style A4 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
|
|
242
336
|
```
|
|
243
337
|
|
|
244
338
|
**Rule of thumb:** If you're reading this README and thinking "this could address several challenges I'm facing," then DxMessaging may be a good fit. If you're thinking "this seems complicated," start with the [Visual Guide](docs/getting-started/visual-guide.md) or stick with simpler patterns.
|
|
245
339
|
|
|
246
|
-
**New to messaging?** Start with the [Visual Guide](docs/getting-started/visual-guide.md) (5 min) for a beginner-friendly introduction!
|
|
247
|
-
|
|
248
340
|
Looking for hard numbers? See OS-specific [Performance Benchmarks](docs/architecture/performance.md).
|
|
249
341
|
|
|
250
342
|
## Why DxMessaging
|
|
@@ -266,7 +358,7 @@ public class GameUI : MonoBehaviour {
|
|
|
266
358
|
|
|
267
359
|
Months later: "Why is our game using 2GB of RAM after an hour?"
|
|
268
360
|
|
|
269
|
-
|
|
361
|
+
#### Scenario 2: The Spaghetti Mess
|
|
270
362
|
|
|
271
363
|
```csharp
|
|
272
364
|
public class GameUI : MonoBehaviour {
|
|
@@ -289,7 +381,7 @@ public class GameUI : MonoBehaviour {
|
|
|
289
381
|
|
|
290
382
|
**Your UI now depends on many systems.** Refactoring becomes more difficult.
|
|
291
383
|
|
|
292
|
-
|
|
384
|
+
#### Scenario 3: The Debugging Black Hole
|
|
293
385
|
|
|
294
386
|
Player reports: "My health bar didn't update!"
|
|
295
387
|
|
|
@@ -326,7 +418,7 @@ public class GameUI : MessageAwareComponent {
|
|
|
326
418
|
|
|
327
419
|
###### Automatic lifecycle = leaks are prevented by default
|
|
328
420
|
|
|
329
|
-
|
|
421
|
+
##### Scenario 2: No More Coupling
|
|
330
422
|
|
|
331
423
|
```csharp
|
|
332
424
|
public class GameUI : MessageAwareComponent {
|
|
@@ -344,7 +436,7 @@ public class GameUI : MessageAwareComponent {
|
|
|
344
436
|
|
|
345
437
|
**Your UI is now independent.** Swapping systems no longer requires updating UI references.
|
|
346
438
|
|
|
347
|
-
|
|
439
|
+
##### Scenario 3: Debugging is Built In
|
|
348
440
|
|
|
349
441
|
Open any `MessageAwareComponent` in the Inspector:
|
|
350
442
|
|
|
@@ -448,9 +540,6 @@ flowchart LR
|
|
|
448
540
|
P[Producer] --> I[Interceptors<br/>validate/mutate]
|
|
449
541
|
I --> H[Handlers<br/>main logic]
|
|
450
542
|
H --> PP[Post-Processors<br/>analytics/logging]
|
|
451
|
-
style I fill:#ffe7ba,stroke:#d48806,stroke-width:2px,color:#000
|
|
452
|
-
style H fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
|
|
453
|
-
style PP fill:#b7eb8f,stroke:#389e0d,stroke-width:2px,color:#000
|
|
454
543
|
```
|
|
455
544
|
|
|
456
545
|
### Global Observers: Listen to All Events
|
|
@@ -758,7 +847,7 @@ For OS-specific benchmark tables generated by PlayMode tests, see [Performance B
|
|
|
758
847
|
| **Interceptors** | ✅ Pipeline | ❌ No | ⚠️ Filters | ❌ No |
|
|
759
848
|
| **Post-Processing** | ✅ Dedicated | ❌ No | ⚠️ Filters | ❌ No |
|
|
760
849
|
| **Stream Operators** | ❌ No | ✅ Extensive | ❌ No | ⚠️ With UniRx |
|
|
761
|
-
| **Performance** | ✅ Good (
|
|
850
|
+
| **Performance** | ✅ Good (10-17M) | ✅ Good (18M) | ✅ High (97M) | ⚠️ Moderate (2.5M) |
|
|
762
851
|
| **Dependencies** | ✅ None | ⚠️ UniTask | ✅ None | ⚠️ Zenject |
|
|
763
852
|
|
|
764
853
|
### Comparison with Traditional Approaches
|
package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"UniRx",
|
|
13
13
|
"UniTask"
|
|
14
14
|
],
|
|
15
|
-
"includePlatforms": [],
|
|
15
|
+
"includePlatforms": ["Editor"],
|
|
16
16
|
"excludePlatforms": [],
|
|
17
17
|
"allowUnsafeCode": false,
|
|
18
18
|
"overrideReferences": true,
|
|
@@ -35,11 +35,6 @@
|
|
|
35
35
|
"expression": "0.0.1",
|
|
36
36
|
"define": "UNIRX_PRESENT"
|
|
37
37
|
},
|
|
38
|
-
{
|
|
39
|
-
"name": "com.svermeulen.extenject",
|
|
40
|
-
"expression": "0.0.1",
|
|
41
|
-
"define": "ZENJECT_PRESENT"
|
|
42
|
-
},
|
|
43
38
|
{
|
|
44
39
|
"name": "com.svermeulen.extenject",
|
|
45
40
|
"expression": "0.0.1",
|