@3fn/core 13.0.0 → 14.0.0
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/.kiro/agents/ada-prompt.md +80 -132
- package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
- package/.kiro/agents/ada.json +44 -59
- package/.kiro/agents/ada.json.attribution.json +13 -0
- package/.kiro/agents/data-prompt.md +83 -74
- package/.kiro/agents/data-prompt.md.attribution.json +53 -0
- package/.kiro/agents/data.json +31 -38
- package/.kiro/agents/data.json.attribution.json +13 -0
- package/.kiro/agents/kenya-prompt.md +83 -72
- package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
- package/.kiro/agents/kenya.json +27 -35
- package/.kiro/agents/kenya.json.attribution.json +13 -0
- package/.kiro/agents/leonardo-prompt.md +176 -234
- package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
- package/.kiro/agents/leonardo.json +28 -36
- package/.kiro/agents/leonardo.json.attribution.json +13 -0
- package/.kiro/agents/lina-prompt.md +110 -151
- package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
- package/.kiro/agents/lina.json +46 -59
- package/.kiro/agents/lina.json.attribution.json +13 -0
- package/.kiro/agents/sparky-prompt.md +89 -72
- package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
- package/.kiro/agents/sparky.json +37 -37
- package/.kiro/agents/sparky.json.attribution.json +13 -0
- package/.kiro/agents/stacy-prompt.md +74 -48
- package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
- package/.kiro/agents/stacy.json +28 -30
- package/.kiro/agents/stacy.json.attribution.json +13 -0
- package/.kiro/agents/thurgood-prompt.md +94 -138
- package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
- package/.kiro/agents/thurgood.json +31 -35
- package/.kiro/agents/thurgood.json.attribution.json +13 -0
- package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
- package/.kiro/steering/Civitas-System-Overview.md +5 -5
- package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
- package/.kiro/steering/Task-Completion-Protocol.md +98 -17
- package/.kiro/steering/core-goals.md +3 -3
- package/.kiro/steering/personal-note.md +1 -1
- package/.kiro/steering/start-up-tasks.md +14 -3
- package/application-mcp-server/src/index.ts +26 -0
- package/dist/ComponentTokens.android.kt +1 -1
- package/dist/ComponentTokens.ios.swift +1 -1
- package/dist/ComponentTokens.web.css +1 -1
- package/dist/DesignTokens.android.kt +1 -1
- package/dist/DesignTokens.dtcg.json +8 -5
- package/dist/DesignTokens.figma.json +2 -2
- package/dist/DesignTokens.ios.swift +1 -1
- package/dist/DesignTokens.web.css +1 -1
- package/dist/android/DesignTokens.android.kt +1 -1
- package/dist/blend/OklchBlendCalculator.js +1 -0
- package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
- package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
- package/dist/browser/designerpunk.esm.js +22 -81
- package/dist/browser/designerpunk.esm.min.js +29 -32
- package/dist/browser/designerpunk.umd.js +22 -81
- package/dist/browser/designerpunk.umd.min.js +42 -45
- package/dist/browser/tokens.css +1 -1
- package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
- package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
- package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
- package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
- package/dist/components/core/Button-CTA/types.d.ts +0 -24
- package/dist/components/core/Button-CTA/types.js +6 -0
- package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
- package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
- package/dist/generators/DTCGFormatGenerator.js +8 -0
- package/dist/integration/BuildErrorHandler.js +2 -2
- package/dist/ios/DesignTokens.ios.swift +1 -1
- package/dist/mcp/application-mcp.js +24 -0
- package/dist/mcp/docs-mcp.js +130 -15
- package/dist/mcp/product-mcp.js +25 -0
- package/dist/tokens/OpacityTokens.js +1 -1
- package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
- package/dist/tokens/semantic/BlendTokens.js +17 -5
- package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
- package/dist/tokens/semantic/OpacityTokens.js +4 -4
- package/dist/types/ComponentTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.d.ts +1 -1
- package/dist/types/generated/TokenTypes.js +1 -1
- package/dist/validators/StemmaTokenUsageValidator.js +3 -2
- package/dist/web/DesignTokens.web.css +1 -1
- package/governance/Component-Development-Guide.md +22 -12
- package/governance/Component-Development-Standards.md +2 -2
- package/governance/Component-Family-Avatar.md +0 -1
- package/governance/Component-Family-Badge.md +0 -1
- package/governance/Component-Family-Button.md +4 -17
- package/governance/Component-Family-Chip.md +0 -1
- package/governance/Component-Family-Container.md +0 -1
- package/governance/Component-Family-Data-Display.md +1 -2
- package/governance/Component-Family-Divider.md +1 -2
- package/governance/Component-Family-Form-Inputs.md +5 -5
- package/governance/Component-Family-Icon.md +1 -2
- package/governance/Component-Family-Loading.md +1 -2
- package/governance/Component-Family-Modal.md +1 -2
- package/governance/Component-Family-Navigation.md +0 -1
- package/governance/Component-Family-Progress.md +0 -1
- package/governance/Component-Inheritance-Structures.md +195 -76
- package/governance/Component-MCP-Document-Template.md +6 -5
- package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
- package/governance/Component-Quick-Reference.md +33 -33
- package/governance/Component-Readiness-Status.md +54 -38
- package/governance/Component-Templates.md +32 -36
- package/governance/Contract-System-Reference.md +6 -6
- package/governance/MCP-Integration-Guide.md +1 -1
- package/governance/Process-Cross-Reference-Standards.md +31 -13
- package/governance/Process-Development-Workflow.md +49 -59
- package/governance/Process-File-Organization.md +24 -24
- package/governance/Process-Hook-Operations.md +19 -7
- package/governance/Process-Orchestration-Model-Selection.md +92 -0
- package/governance/Process-Spec-Planning.md +91 -39
- package/governance/Process-Task-Type-Definitions.md +80 -4
- package/governance/Product-Handoff-Protocol.md +2 -0
- package/governance/Rosetta-System-Architecture.md +6 -6
- package/governance/Test-Behavioral-Contract-Validation.md +38 -31
- package/governance/Test-Failure-Audit-Methodology.md +1 -1
- package/governance/Token-Family-Accessibility.md +1 -2
- package/governance/Token-Family-Blend.md +18 -16
- package/governance/Token-Family-Blur.md +0 -1
- package/governance/Token-Family-Border.md +1 -2
- package/governance/Token-Family-Color.md +0 -1
- package/governance/Token-Family-Glow.md +1 -2
- package/governance/Token-Family-Layering.md +0 -1
- package/governance/Token-Family-Motion.md +1 -2
- package/governance/Token-Family-Opacity.md +0 -1
- package/governance/Token-Family-Radius.md +1 -2
- package/governance/Token-Family-Responsive.md +1 -2
- package/governance/Token-Family-Shadow.md +1 -2
- package/governance/Token-Family-Sizing.md +0 -1
- package/governance/Token-Family-Spacing.md +1 -2
- package/governance/Token-Family-Typography.md +1 -2
- package/governance/Token-Governance.md +8 -8
- package/governance/Token-Quick-Reference.md +21 -21
- package/governance/Token-Resolution-Patterns.md +1 -1
- package/governance/Token-Semantic-Structure.md +1 -1
- package/governance/Web-Authoring-Standards.md +5 -5
- package/governance/browser-distribution-guide.md +1 -4
- package/governance/classification-map.md +368 -0
- package/governance/completion-documentation-guide.md +11 -8
- package/governance/component-meta-authoring-guide.md +1 -1
- package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
- package/governance/platform-implementation-guidelines.md +1 -1
- package/governance/release-management-system.md +2 -2
- package/governance/rosetta-system-principles.md +8 -6
- package/governance/stemma-system-principles.md +18 -17
- package/mcp-server/src/index.ts +24 -6
- package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
- package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
- package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
- package/mcp-server/src/indexer/index-health.ts +27 -2
- package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
- package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
- package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
- package/mcp-server/src/tools/list-cross-references.ts +2 -2
- package/package.json +23 -21
- package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
- package/src/__tests__/console-allowlist.json +14 -0
- package/src/__tests__/console-fail-setup.ts +169 -0
- package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
- package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
- package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
- package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
- package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
- package/src/blend/OklchBlendCalculator.ts +3 -0
- package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
- package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
- package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
- package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
- package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
- package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
- package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
- package/src/cli/__tests__/init.test.ts +45 -11
- package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
- package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
- package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
- package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
- package/src/components/core/Avatar-Base/contracts.yaml +11 -1
- package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
- package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
- package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
- package/src/components/core/Button-CTA/README.md +3 -6
- package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
- package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
- package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
- package/src/components/core/Button-CTA/contracts.yaml +6 -29
- package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
- package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
- package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
- package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
- package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
- package/src/components/core/Button-CTA/types.ts +10 -28
- package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
- package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
- package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
- package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
- package/src/components/core/Input-Text-Base/README.md +25 -2
- package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
- package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
- package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
- package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
- package/src/components/core/Input-Text-Base/types.ts +13 -1
- package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
- package/src/components/core/Input-Text-Email/README.md +8 -7
- package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
- package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
- package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
- package/src/components/core/Input-Text-Password/README.md +9 -8
- package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
- package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
- package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
- package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
- package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
- package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
- package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
- package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
- package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
- package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
- package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
- package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
- package/src/generators/DTCGFormatGenerator.ts +6 -0
- package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
- package/src/integration/BuildErrorHandler.ts +2 -2
- package/src/tokens/OpacityTokens.ts +1 -1
- package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
- package/src/tokens/semantic/BlendTokens.ts +26 -5
- package/src/tokens/semantic/OpacityTokens.ts +4 -4
- package/src/tools/release/__tests__/ReleasePipeline.test.ts +1 -1
- package/src/types/ComponentTypes.ts +1 -1
- package/src/types/generated/TokenTypes.ts +1 -1
- package/src/validators/StemmaTokenUsageValidator.ts +3 -2
- package/token-index/semantics.yaml +1 -2
|
@@ -212,14 +212,20 @@ describe('Avatar Component Accessibility', () => {
|
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
it('should apply empty alt when alt is empty string', async () => {
|
|
215
|
+
// Spec 126 (O2): uses the raw-attribute form (setAttribute) to produce a
|
|
216
|
+
// genuine alt="" — the `alt` *property* setter treats '' as falsy and calls
|
|
217
|
+
// removeAttribute, collapsing `avatar.alt = ''` to the same "absent" state as
|
|
218
|
+
// omitting alt entirely (see Avatar.web.ts `set alt`). Only the attribute form
|
|
219
|
+
// yields a real empty-string alt, and under the settled warn condition
|
|
220
|
+
// (`alt == null`), a real empty-string alt does not warn — so no spy is needed.
|
|
215
221
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
216
222
|
avatar.type = 'human';
|
|
217
223
|
avatar.src = 'https://example.com/profile.jpg';
|
|
218
|
-
avatar.alt
|
|
224
|
+
avatar.setAttribute('alt', '');
|
|
219
225
|
document.body.appendChild(avatar);
|
|
220
|
-
|
|
226
|
+
|
|
221
227
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
222
|
-
|
|
228
|
+
|
|
223
229
|
const img = avatar.shadowRoot?.querySelector('.avatar__image');
|
|
224
230
|
expect(img).toBeTruthy();
|
|
225
231
|
expect(img?.getAttribute('alt')).toBe('');
|
|
@@ -285,16 +291,124 @@ describe('Avatar Component Accessibility', () => {
|
|
|
285
291
|
|
|
286
292
|
it('should NOT warn when no src is provided', async () => {
|
|
287
293
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
288
|
-
|
|
294
|
+
|
|
289
295
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
290
296
|
avatar.type = 'human';
|
|
291
297
|
// No src, no alt
|
|
292
298
|
document.body.appendChild(avatar);
|
|
293
|
-
|
|
299
|
+
|
|
294
300
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
295
|
-
|
|
301
|
+
|
|
296
302
|
expect(consoleSpy).not.toHaveBeenCalled();
|
|
297
|
-
|
|
303
|
+
|
|
304
|
+
consoleSpy.mockRestore();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// Alt Text Warning — Decorative & Type-Scope Edge Semantics (Spec 126, O2)
|
|
310
|
+
// ============================================================================
|
|
311
|
+
//
|
|
312
|
+
// Settled warn condition: `type === 'human' && src && alt == null && !decorative`
|
|
313
|
+
// @see Requirements: 5.4 - Human type + src SHALL require alt for accessibility
|
|
314
|
+
// @see Requirements: 5 AC 5 - Agent type SHALL ignore src entirely
|
|
315
|
+
// @see .kiro/specs/126-avatar-decorative-warn/design-outline.md § 4/5
|
|
316
|
+
//
|
|
317
|
+
// Together with the sentinel test above ('should warn when src is provided
|
|
318
|
+
// without alt'), this block forms the six-test matrix settled by Spec 126 § 5
|
|
319
|
+
// item 2. The sentinel is left unchanged (Spec 126 § 7 non-regression signal).
|
|
320
|
+
describe('Alt Text Warning — Decorative & Type-Scope Edge Semantics (Spec 126)', () => {
|
|
321
|
+
it('should NOT warn when alt="" is set via raw attribute (explicit empty, not "absent")', async () => {
|
|
322
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
323
|
+
|
|
324
|
+
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
325
|
+
avatar.type = 'human';
|
|
326
|
+
avatar.src = 'https://example.com/profile.jpg';
|
|
327
|
+
// Raw attribute form is the only path that yields a genuine alt="" (getter
|
|
328
|
+
// returns '', not null) — the `alt` property setter collapses falsy values to
|
|
329
|
+
// removeAttribute, so this test must use setAttribute to exercise the real case.
|
|
330
|
+
avatar.setAttribute('alt', '');
|
|
331
|
+
document.body.appendChild(avatar);
|
|
332
|
+
|
|
333
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
334
|
+
|
|
335
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
336
|
+
|
|
337
|
+
consoleSpy.mockRestore();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should warn when alt is set to empty string via the property (collapses to null via the setter)', async () => {
|
|
341
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
342
|
+
|
|
343
|
+
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
344
|
+
avatar.type = 'human';
|
|
345
|
+
avatar.src = 'https://example.com/profile.jpg';
|
|
346
|
+
// Property-form empty string: the `alt` setter treats '' as falsy and calls
|
|
347
|
+
// removeAttribute('alt'), so this lands in the "alt absent" bucket and warns —
|
|
348
|
+
// pins the attribute-vs-property collapse explicitly (Spec 126 § 5 item 2).
|
|
349
|
+
avatar.alt = '';
|
|
350
|
+
document.body.appendChild(avatar);
|
|
351
|
+
|
|
352
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
353
|
+
|
|
354
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
355
|
+
expect.stringContaining('alt')
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
consoleSpy.mockRestore();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should NOT warn when decorative is true, even without alt (co-asserted with aria-hidden)', async () => {
|
|
362
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
363
|
+
|
|
364
|
+
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
365
|
+
avatar.type = 'human';
|
|
366
|
+
avatar.src = 'https://example.com/profile.jpg';
|
|
367
|
+
avatar.decorative = true;
|
|
368
|
+
// No alt provided
|
|
369
|
+
document.body.appendChild(avatar);
|
|
370
|
+
|
|
371
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
372
|
+
|
|
373
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
374
|
+
|
|
375
|
+
// Co-assert the semantic linkage (Req 9.2): the warn is correctly suppressed
|
|
376
|
+
// because the avatar is genuinely hidden from screen readers, not incidentally.
|
|
377
|
+
const container = avatar.shadowRoot?.querySelector('.avatar');
|
|
378
|
+
expect(container?.getAttribute('aria-hidden')).toBe('true');
|
|
379
|
+
|
|
380
|
+
consoleSpy.mockRestore();
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should NOT warn for agent type with src and no alt (Req 5 AC 5 — agent ignores src entirely)', async () => {
|
|
384
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
385
|
+
|
|
386
|
+
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
387
|
+
avatar.type = 'agent';
|
|
388
|
+
avatar.src = 'https://example.com/profile.jpg';
|
|
389
|
+
// No alt provided
|
|
390
|
+
document.body.appendChild(avatar);
|
|
391
|
+
|
|
392
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
393
|
+
|
|
394
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
395
|
+
|
|
396
|
+
consoleSpy.mockRestore();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should NOT warn for agent type with src and alt both present (confirms silencing is type-driven, not alt-driven)', async () => {
|
|
400
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
401
|
+
|
|
402
|
+
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
403
|
+
avatar.type = 'agent';
|
|
404
|
+
avatar.src = 'https://example.com/profile.jpg';
|
|
405
|
+
avatar.alt = 'Ignored for agent type';
|
|
406
|
+
document.body.appendChild(avatar);
|
|
407
|
+
|
|
408
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
409
|
+
|
|
410
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
411
|
+
|
|
298
412
|
consoleSpy.mockRestore();
|
|
299
413
|
});
|
|
300
414
|
});
|
|
@@ -287,6 +287,7 @@ describe('Avatar Component Image Handling', () => {
|
|
|
287
287
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
288
288
|
avatar.type = 'agent';
|
|
289
289
|
avatar.src = 'https://example.com/agent-image.jpg';
|
|
290
|
+
avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
|
|
290
291
|
document.body.appendChild(avatar);
|
|
291
292
|
|
|
292
293
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
@@ -299,6 +300,7 @@ describe('Avatar Component Image Handling', () => {
|
|
|
299
300
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
300
301
|
avatar.type = 'agent';
|
|
301
302
|
avatar.src = 'https://example.com/agent-image.jpg';
|
|
303
|
+
avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
|
|
302
304
|
document.body.appendChild(avatar);
|
|
303
305
|
|
|
304
306
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
@@ -311,6 +313,7 @@ describe('Avatar Component Image Handling', () => {
|
|
|
311
313
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
312
314
|
avatar.type = 'agent';
|
|
313
315
|
avatar.src = 'https://example.com/agent-image.jpg';
|
|
316
|
+
avatar.alt = 'Agent profile'; // honor the alt-with-src contract (Req 5.4)
|
|
314
317
|
document.body.appendChild(avatar);
|
|
315
318
|
|
|
316
319
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
@@ -218,27 +218,33 @@ describe('Avatar Component Core API', () => {
|
|
|
218
218
|
// ============================================================================
|
|
219
219
|
|
|
220
220
|
describe('Src Prop', () => {
|
|
221
|
+
// Fixtures set `alt` alongside `src` so they honor the component's own a11y
|
|
222
|
+
// contract (Req 5.4: alt is required with src) — otherwise the component
|
|
223
|
+
// rightly warns, polluting CI logs. The assertions below are about src only.
|
|
221
224
|
it('should accept src value', () => {
|
|
222
225
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
223
226
|
avatar.src = 'https://example.com/image.jpg';
|
|
227
|
+
avatar.alt = 'Test avatar';
|
|
224
228
|
document.body.appendChild(avatar);
|
|
225
|
-
|
|
229
|
+
|
|
226
230
|
expect(avatar.src).toBe('https://example.com/image.jpg');
|
|
227
231
|
expect(avatar.getAttribute('src')).toBe('https://example.com/image.jpg');
|
|
228
232
|
});
|
|
229
233
|
|
|
230
234
|
it('should update src via attribute', () => {
|
|
231
235
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
236
|
+
avatar.alt = 'Test avatar';
|
|
232
237
|
document.body.appendChild(avatar);
|
|
233
|
-
|
|
238
|
+
|
|
234
239
|
avatar.setAttribute('src', 'https://example.com/new-image.jpg');
|
|
235
240
|
expect(avatar.src).toBe('https://example.com/new-image.jpg');
|
|
236
241
|
});
|
|
237
242
|
|
|
238
243
|
it('should update src via property', () => {
|
|
239
244
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
245
|
+
avatar.alt = 'Test avatar';
|
|
240
246
|
document.body.appendChild(avatar);
|
|
241
|
-
|
|
247
|
+
|
|
242
248
|
avatar.src = 'https://example.com/profile.png';
|
|
243
249
|
expect(avatar.getAttribute('src')).toBe('https://example.com/profile.png');
|
|
244
250
|
});
|
|
@@ -246,8 +252,9 @@ describe('Avatar Component Core API', () => {
|
|
|
246
252
|
it('should remove src attribute when set to null', () => {
|
|
247
253
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
248
254
|
avatar.src = 'https://example.com/image.jpg';
|
|
255
|
+
avatar.alt = 'Test avatar';
|
|
249
256
|
document.body.appendChild(avatar);
|
|
250
|
-
|
|
257
|
+
|
|
251
258
|
avatar.src = null;
|
|
252
259
|
expect(avatar.src).toBeNull();
|
|
253
260
|
expect(avatar.hasAttribute('src')).toBe(false);
|
|
@@ -566,8 +573,9 @@ describe('Avatar Component Core API', () => {
|
|
|
566
573
|
|
|
567
574
|
it('should reflect src property to attribute', () => {
|
|
568
575
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
576
|
+
avatar.alt = 'Test avatar'; // honor the alt-with-src contract (Req 5.4)
|
|
569
577
|
document.body.appendChild(avatar);
|
|
570
|
-
|
|
578
|
+
|
|
571
579
|
avatar.src = 'https://example.com/test.jpg';
|
|
572
580
|
expect(avatar.getAttribute('src')).toBe('https://example.com/test.jpg');
|
|
573
581
|
});
|
|
@@ -629,8 +637,9 @@ describe('Avatar Component Core API', () => {
|
|
|
629
637
|
it('should read src attribute as property', () => {
|
|
630
638
|
const avatar = document.createElement('avatar-base') as AvatarBaseElement;
|
|
631
639
|
avatar.setAttribute('src', 'https://example.com/img.png');
|
|
640
|
+
avatar.setAttribute('alt', 'Test avatar'); // honor the alt-with-src contract (Req 5.4)
|
|
632
641
|
document.body.appendChild(avatar);
|
|
633
|
-
|
|
642
|
+
|
|
634
643
|
expect(avatar.src).toBe('https://example.com/img.png');
|
|
635
644
|
});
|
|
636
645
|
|
|
@@ -162,7 +162,17 @@ contracts:
|
|
|
162
162
|
When not decorative, Avatar provides accessible name:
|
|
163
163
|
- If alt prop provided: uses alt text
|
|
164
164
|
- If no alt prop (iOS/Android): "User avatar" for human, "AI agent avatar" for agent
|
|
165
|
-
- Web requires alt prop when src is provided
|
|
165
|
+
- Web requires alt prop when src is provided, scoped to human type only
|
|
166
|
+
(agent type ignores src entirely — Req 5 AC 5) and only when decorative
|
|
167
|
+
is false (decorative is the component's own non-informative signal,
|
|
168
|
+
Req 9.2, and takes precedence over the alt requirement)
|
|
169
|
+
- Web edge semantics (settled Spec 126, O2, ratified 2026-07-09): the dev
|
|
170
|
+
warn fires iff `type === 'human' && src && alt == null && !decorative`.
|
|
171
|
+
A raw explicit empty alt (`setAttribute('alt', '')`) does not warn —
|
|
172
|
+
it is treated as the deliberate empty-alt signal. The `alt` property
|
|
173
|
+
setter collapses a falsy value (including '') to removeAttribute, so
|
|
174
|
+
setting `avatar.alt = ''` via the property lands in the "absent" bucket
|
|
175
|
+
and DOES warn — this is a setter-collapse detail, not a design gap.
|
|
166
176
|
wcag: "1.1.1 Non-text Content"
|
|
167
177
|
platforms: [web, ios, android]
|
|
168
178
|
validation:
|
|
@@ -700,12 +700,31 @@ export class AvatarBaseElement extends HTMLElement {
|
|
|
700
700
|
// @see Requirements: 9.2 - Apply aria-hidden="true" when decorative prop is true
|
|
701
701
|
const ariaHiddenAttr = decorative ? ' aria-hidden="true"' : '';
|
|
702
702
|
|
|
703
|
-
// Warn in development if
|
|
704
|
-
// @see Requirements: 5.4 -
|
|
705
|
-
|
|
703
|
+
// Warn in development if a human-type avatar is missing alt text for its image.
|
|
704
|
+
// @see Requirements: 5.4 - Human type + src SHALL require alt for accessibility
|
|
705
|
+
// @see Requirements: 5 AC 5 - Agent type SHALL ignore src entirely (src not
|
|
706
|
+
// rendered as an image), so agent-type avatars never warn regardless of alt.
|
|
707
|
+
//
|
|
708
|
+
// Settled edge semantics (Spec 126, O2, ratified 2026-07-09):
|
|
709
|
+
// - Scope is human-type only — Req 5.4 is human-type-scoped, and Req 5 AC 5
|
|
710
|
+
// affirmatively establishes that agent-type SHALL ignore src, so a src+no-alt
|
|
711
|
+
// agent avatar is not a violation of anything.
|
|
712
|
+
// - `alt == null` (loose equality — also excludes `undefined`), not `!alt`, is the
|
|
713
|
+
// test. The `alt` attribute getter returns `getAttribute('alt')`. Raw
|
|
714
|
+
// `setAttribute('alt', '')` yields `alt === ''`, which is the HTML-idiomatic
|
|
715
|
+
// explicit-empty-alt signal and must NOT warn. The `alt` *property setter*
|
|
716
|
+
// (see below) treats a falsy value as "remove the attribute," so
|
|
717
|
+
// `avatar.alt = ''` collapses to `alt === null` — indistinguishable from
|
|
718
|
+
// omitting alt entirely, and DOES warn under this condition.
|
|
719
|
+
// - `!decorative` is the operative fix for the confirmed false positive: a
|
|
720
|
+
// `decorative` avatar is the component's official signal that the image is
|
|
721
|
+
// non-informative (Req 9.2 applies `aria-hidden="true"`), so it must never warn
|
|
722
|
+
// regardless of alt.
|
|
723
|
+
if (type === 'human' && src && alt == null && !decorative && typeof console !== 'undefined') {
|
|
706
724
|
console.warn(
|
|
707
|
-
'AvatarBaseElement: "alt"
|
|
708
|
-
'
|
|
725
|
+
'AvatarBaseElement: "alt" is required when "src" is provided. ' +
|
|
726
|
+
'Add an alt description of the image; or, if this avatar is purely decorative ' +
|
|
727
|
+
'(non-informative), set the "decorative" prop to hide it from screen readers.'
|
|
709
728
|
);
|
|
710
729
|
}
|
|
711
730
|
|
|
@@ -102,7 +102,7 @@ contracts:
|
|
|
102
102
|
- No hover or press visual feedback
|
|
103
103
|
- pointer-events: none (web)
|
|
104
104
|
- No gesture handlers (native)
|
|
105
|
-
wcag: null
|
|
105
|
+
wcag: "N/A" # legitimate-null: non-interactive negative guarantee, no positive WCAG SC (125-B 4.2)
|
|
106
106
|
platforms: [web, ios, android]
|
|
107
107
|
validation:
|
|
108
108
|
- Badge cannot receive keyboard focus
|
|
@@ -77,7 +77,7 @@ contracts:
|
|
|
77
77
|
- No hover or press visual feedback
|
|
78
78
|
- pointer-events: none (web)
|
|
79
79
|
- No gesture handlers (native)
|
|
80
|
-
wcag: null
|
|
80
|
+
wcag: "N/A" # legitimate-null: non-interactive negative guarantee, no positive WCAG SC (125-B 4.2)
|
|
81
81
|
platforms: [web, ios, android]
|
|
82
82
|
validation:
|
|
83
83
|
- Badge cannot receive keyboard focus
|
|
@@ -90,20 +90,12 @@ properties:
|
|
|
90
90
|
When true, text is truncated with ellipsis instead of wrapping.
|
|
91
91
|
Default (false) allows text wrapping for accessibility and i18n support.
|
|
92
92
|
|
|
93
|
-
disabled:
|
|
94
|
-
type: boolean
|
|
95
|
-
required: false
|
|
96
|
-
default: false
|
|
97
|
-
description: |
|
|
98
|
-
When true, button is non-interactive with disabled visual styling.
|
|
99
|
-
Disabled buttons are not keyboard focusable and onPress is not called.
|
|
100
|
-
|
|
101
93
|
onPress:
|
|
102
94
|
type: "() => void"
|
|
103
95
|
required: true
|
|
104
96
|
description: |
|
|
105
97
|
Callback function invoked when button is clicked, tapped, or activated
|
|
106
|
-
via keyboard (Enter/Space).
|
|
98
|
+
via keyboard (Enter/Space).
|
|
107
99
|
|
|
108
100
|
testID:
|
|
109
101
|
type: string
|
|
@@ -153,7 +145,6 @@ tokens:
|
|
|
153
145
|
blend:
|
|
154
146
|
- blend.hoverDarker
|
|
155
147
|
- blend.pressedDarker
|
|
156
|
-
- blend.disabledDesaturate
|
|
157
148
|
- blend.iconLighter
|
|
158
149
|
component:
|
|
159
150
|
- buttonCTA.minWidth.small
|
|
@@ -247,11 +238,10 @@ accessibility:
|
|
|
247
238
|
- "1.4.11 Non-text Contrast - Focus ring meets 3:1 contrast"
|
|
248
239
|
- "2.1.1 Keyboard - Fully keyboard accessible (Tab, Enter, Space)"
|
|
249
240
|
- "2.4.7 Focus Visible - Clear focus indicator with :focus-visible"
|
|
250
|
-
- "4.1.2 Name, Role, Value - Proper ARIA attributes
|
|
241
|
+
- "4.1.2 Name, Role, Value - Proper ARIA attributes"
|
|
251
242
|
- "2.5.5 Target Size - Touch targets meet 44px minimum"
|
|
252
243
|
aria_attributes:
|
|
253
244
|
- role: button (implicit from semantic element)
|
|
254
|
-
- aria-disabled: true when disabled
|
|
255
245
|
- aria-label: label prop value
|
|
256
246
|
keyboard_navigation:
|
|
257
247
|
- Tab: Move focus to/from button
|
|
@@ -41,10 +41,11 @@ This component guarantees 7 behavioral contracts across all platforms:
|
|
|
41
41
|
| `pressable` | Responds to press/click events | web, ios, android | 2.1.1 |
|
|
42
42
|
| `hover_state` | Visual feedback on hover | web | 1.4.13 |
|
|
43
43
|
| `pressed_state` | Visual feedback when pressed | web, ios, android | 2.4.7 |
|
|
44
|
-
| `disabled_state` | Prevents interaction when disabled | web, ios, android | 4.1.2 |
|
|
45
44
|
| `loading_state` | Shows loading indicator | web, ios, android | 4.1.3 |
|
|
46
45
|
| `focus_ring` | WCAG 2.4.7 focus visible indicator | web, ios, android | 2.4.7 |
|
|
47
46
|
|
|
47
|
+
**Excluded**: `state_disabled` — DesignerPunk does not support disabled states for usability and accessibility reasons. If an action is unavailable, the component should not be rendered (adjudicated 2026-07-15).
|
|
48
|
+
|
|
48
49
|
---
|
|
49
50
|
|
|
50
51
|
## Usage
|
|
@@ -62,7 +63,6 @@ This component guarantees 7 behavioral contracts across all platforms:
|
|
|
62
63
|
variant="primary"
|
|
63
64
|
icon="arrow-right"
|
|
64
65
|
no-wrap="false"
|
|
65
|
-
disabled="false"
|
|
66
66
|
test-id="submit-button"
|
|
67
67
|
></button-cta>
|
|
68
68
|
```
|
|
@@ -122,7 +122,6 @@ ButtonCTA(
|
|
|
122
122
|
| `variant` | `'primary' \| 'secondary' \| 'tertiary'` | No | `'primary'` | Button visual variant |
|
|
123
123
|
| `icon` | `IconName` | No | - | Optional leading icon |
|
|
124
124
|
| `noWrap` | `boolean` | No | `false` | Prevent text wrapping |
|
|
125
|
-
| `disabled` | `boolean` | No | `false` | Disable button interaction |
|
|
126
125
|
| `onPress` | `() => void` | ✅ Yes | - | Press/click handler |
|
|
127
126
|
| `testID` | `string` | No | - | Test identifier |
|
|
128
127
|
|
|
@@ -177,7 +176,6 @@ ButtonCTA(
|
|
|
177
176
|
### Blend
|
|
178
177
|
- `blend.hoverDarker` - Hover state (8% darker)
|
|
179
178
|
- `blend.pressedDarker` - Pressed state (12% darker)
|
|
180
|
-
- `blend.disabledDesaturate` - Disabled state (12% less saturated)
|
|
181
179
|
- `blend.iconLighter` - Icon optical balance (8% lighter)
|
|
182
180
|
|
|
183
181
|
### Accessibility
|
|
@@ -205,7 +203,7 @@ ButtonCTA(
|
|
|
205
203
|
| 2.1.1 Keyboard | ✅ | Fully keyboard accessible (Tab, Enter, Space) |
|
|
206
204
|
| 2.4.7 Focus Visible | ✅ | Clear focus indicator with :focus-visible |
|
|
207
205
|
| 2.5.5 Target Size | ✅ | Touch targets meet 44px minimum |
|
|
208
|
-
| 4.1.2 Name, Role, Value | ✅ | Proper ARIA attributes
|
|
206
|
+
| 4.1.2 Name, Role, Value | ✅ | Proper ARIA attributes |
|
|
209
207
|
|
|
210
208
|
### Keyboard Navigation
|
|
211
209
|
|
|
@@ -219,7 +217,6 @@ ButtonCTA(
|
|
|
219
217
|
|
|
220
218
|
- Semantic `<button>` element provides implicit role
|
|
221
219
|
- `aria-label` set from label prop
|
|
222
|
-
- `aria-disabled="true"` when disabled
|
|
223
220
|
- State changes announced to assistive technology
|
|
224
221
|
|
|
225
222
|
---
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Unit tests for Button-CTA component rendering
|
|
8
8
|
*
|
|
9
9
|
* Tests component rendering with various props, size variants, variant styles,
|
|
10
|
-
* icon integration, text wrapping
|
|
10
|
+
* icon integration, and text wrapping.
|
|
11
11
|
*
|
|
12
12
|
* Stemma System Naming: [Family]-[Type] = Button-CTA
|
|
13
13
|
* Component Type: Standalone (no behavioral variants)
|
|
@@ -66,7 +66,6 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
66
66
|
buttonVariant?: 'primary' | 'secondary' | 'tertiary';
|
|
67
67
|
icon?: string;
|
|
68
68
|
noWrap?: boolean;
|
|
69
|
-
disabled?: boolean;
|
|
70
69
|
testID?: string;
|
|
71
70
|
}): ButtonCTA {
|
|
72
71
|
// Create element - constructor runs and attaches shadow DOM
|
|
@@ -78,7 +77,6 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
78
77
|
if (props.buttonVariant) button.buttonVariant = props.buttonVariant;
|
|
79
78
|
if (props.icon) button.icon = props.icon;
|
|
80
79
|
if (props.noWrap !== undefined) button.noWrap = props.noWrap;
|
|
81
|
-
if (props.disabled !== undefined) button.disabled = props.disabled;
|
|
82
80
|
if (props.testID) button.testID = props.testID;
|
|
83
81
|
|
|
84
82
|
// Append to container
|
|
@@ -287,40 +285,48 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
287
285
|
});
|
|
288
286
|
});
|
|
289
287
|
|
|
290
|
-
describe('Disabled State', () => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
describe('No Disabled State (philosophy exclusion)', () => {
|
|
289
|
+
// DesignerPunk does not support disabled states: if an action is
|
|
290
|
+
// unavailable, the component should not be rendered. state_disabled was
|
|
291
|
+
// removed from Button-CTA per the 2026-07-15 adjudication; these tests
|
|
292
|
+
// guard against the disabled state resurfacing.
|
|
293
|
+
|
|
294
|
+
it('should declare state_disabled under excludes (not contracts) in contracts.yaml', () => {
|
|
295
|
+
const fs = require('fs');
|
|
296
|
+
const path = require('path');
|
|
297
|
+
const contractsContent = fs.readFileSync(
|
|
298
|
+
path.join(__dirname, '../contracts.yaml'),
|
|
299
|
+
'utf-8'
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const contractsSection = contractsContent.split(/^excludes:/m)[0];
|
|
303
|
+
const excludesSection = contractsContent.split(/^excludes:/m)[1] ?? '';
|
|
304
|
+
expect(contractsSection).not.toContain('state_disabled');
|
|
305
|
+
expect(excludesSection).toContain('state_disabled');
|
|
299
306
|
});
|
|
300
|
-
|
|
301
|
-
it('should not
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
expect(shadowButton?.hasAttribute('disabled')).toBe(false);
|
|
307
|
-
expect(shadowButton?.getAttribute('aria-disabled')).toBe('false');
|
|
308
|
-
expect(shadowButton?.className).not.toContain('button-cta--disabled');
|
|
307
|
+
|
|
308
|
+
it('should not expose a disabled property or observe a disabled attribute', () => {
|
|
309
|
+
expect(ButtonCTA.observedAttributes).not.toContain('disabled');
|
|
310
|
+
|
|
311
|
+
const button = createButton({ label: 'Button' });
|
|
312
|
+
expect('disabled' in button).toBe(false);
|
|
309
313
|
});
|
|
310
|
-
|
|
311
|
-
it('should
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
|
|
315
|
+
it('should ignore a disabled attribute set by consumers', () => {
|
|
316
|
+
const button = createButton({ label: 'Button' });
|
|
317
|
+
button.setAttribute('disabled', '');
|
|
318
|
+
|
|
315
319
|
let pressCount = 0;
|
|
316
320
|
button.addEventListener('press', () => {
|
|
317
321
|
pressCount++;
|
|
318
322
|
});
|
|
319
|
-
|
|
323
|
+
|
|
320
324
|
const shadowButton = button.shadowRoot?.querySelector('button');
|
|
325
|
+
expect(shadowButton?.hasAttribute('disabled')).toBe(false);
|
|
326
|
+
expect(shadowButton?.className).not.toContain('button-cta--disabled');
|
|
327
|
+
|
|
321
328
|
shadowButton?.click();
|
|
322
|
-
|
|
323
|
-
expect(pressCount).toBe(0);
|
|
329
|
+
expect(pressCount).toBe(1);
|
|
324
330
|
});
|
|
325
331
|
});
|
|
326
332
|
|
|
@@ -409,21 +415,6 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
409
415
|
expect(pressCount).toBe(1);
|
|
410
416
|
});
|
|
411
417
|
|
|
412
|
-
it('should NOT call onPress when button disabled', () => {
|
|
413
|
-
// Requirement 15.1: Disabled button doesn't trigger onPress
|
|
414
|
-
const button = createButton({ label: 'Disabled Button', disabled: true });
|
|
415
|
-
|
|
416
|
-
let pressCount = 0;
|
|
417
|
-
button.addEventListener('press', () => {
|
|
418
|
-
pressCount++;
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
const shadowButton = button.shadowRoot?.querySelector('button');
|
|
422
|
-
shadowButton?.click();
|
|
423
|
-
|
|
424
|
-
expect(pressCount).toBe(0);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
418
|
it('should receive focus via Tab key', () => {
|
|
428
419
|
// Requirement 15.2: Button is keyboard focusable
|
|
429
420
|
const button = createButton({ label: 'Focusable Button' });
|
|
@@ -647,19 +638,6 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
647
638
|
expect(shadowButton?.getAttribute('role')).toBe('button');
|
|
648
639
|
expect(shadowButton?.getAttribute('aria-label')).toBe('Accessible Button');
|
|
649
640
|
expect(shadowButton?.getAttribute('type')).toBe('button');
|
|
650
|
-
expect(shadowButton?.getAttribute('aria-disabled')).toBe('false');
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
it('should have proper ARIA attributes when disabled', () => {
|
|
654
|
-
// Requirement 12.6: Disabled button has correct ARIA attributes
|
|
655
|
-
const button = createButton({ label: 'Disabled Button', disabled: true });
|
|
656
|
-
|
|
657
|
-
const shadowButton = button.shadowRoot?.querySelector('button');
|
|
658
|
-
|
|
659
|
-
// Verify disabled ARIA attributes
|
|
660
|
-
expect(shadowButton?.getAttribute('role')).toBe('button');
|
|
661
|
-
expect(shadowButton?.getAttribute('aria-disabled')).toBe('true');
|
|
662
|
-
expect(shadowButton?.hasAttribute('disabled')).toBe(true);
|
|
663
641
|
});
|
|
664
642
|
|
|
665
643
|
it('should support keyboard navigation with Enter key', () => {
|
|
@@ -716,37 +694,5 @@ describe('Button-CTA Component Rendering', () => {
|
|
|
716
694
|
expect(pressCount).toBe(1);
|
|
717
695
|
});
|
|
718
696
|
|
|
719
|
-
it('should not activate on keyboard when disabled', () => {
|
|
720
|
-
// Requirement 15.4: Disabled button doesn't respond to keyboard
|
|
721
|
-
const button = createButton({ label: 'Disabled Keyboard', disabled: true });
|
|
722
|
-
|
|
723
|
-
let pressCount = 0;
|
|
724
|
-
button.addEventListener('press', () => {
|
|
725
|
-
pressCount++;
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
const shadowButton = button.shadowRoot?.querySelector('button');
|
|
729
|
-
shadowButton?.focus();
|
|
730
|
-
|
|
731
|
-
// Try Enter key
|
|
732
|
-
const enterEvent = new KeyboardEvent('keydown', {
|
|
733
|
-
key: 'Enter',
|
|
734
|
-
code: 'Enter',
|
|
735
|
-
bubbles: true,
|
|
736
|
-
cancelable: true
|
|
737
|
-
});
|
|
738
|
-
shadowButton?.dispatchEvent(enterEvent);
|
|
739
|
-
|
|
740
|
-
// Try Space key
|
|
741
|
-
const spaceEvent = new KeyboardEvent('keydown', {
|
|
742
|
-
key: ' ',
|
|
743
|
-
code: 'Space',
|
|
744
|
-
bubbles: true,
|
|
745
|
-
cancelable: true
|
|
746
|
-
});
|
|
747
|
-
shadowButton?.dispatchEvent(spaceEvent);
|
|
748
|
-
|
|
749
|
-
expect(pressCount).toBe(0);
|
|
750
|
-
});
|
|
751
697
|
});
|
|
752
698
|
});
|
|
@@ -123,7 +123,6 @@ describe('Button-CTA Test Infrastructure Setup', () => {
|
|
|
123
123
|
buttonVariant: 'secondary',
|
|
124
124
|
icon: 'arrow-right',
|
|
125
125
|
noWrap: true,
|
|
126
|
-
disabled: false,
|
|
127
126
|
testID: 'test-button'
|
|
128
127
|
});
|
|
129
128
|
|
|
@@ -132,7 +131,6 @@ describe('Button-CTA Test Infrastructure Setup', () => {
|
|
|
132
131
|
expect(button.buttonVariant).toBe('secondary');
|
|
133
132
|
expect(button.icon).toBe('arrow-right');
|
|
134
133
|
expect(button.noWrap).toBe(true);
|
|
135
|
-
expect(button.disabled).toBe(false);
|
|
136
134
|
expect(button.testID).toBe('test-button');
|
|
137
135
|
});
|
|
138
136
|
|
|
@@ -96,7 +96,6 @@ export async function createButtonCTA(props: {
|
|
|
96
96
|
buttonVariant?: 'primary' | 'secondary' | 'tertiary';
|
|
97
97
|
icon?: string;
|
|
98
98
|
noWrap?: boolean;
|
|
99
|
-
disabled?: boolean;
|
|
100
99
|
testID?: string;
|
|
101
100
|
}): Promise<ButtonCTA> {
|
|
102
101
|
registerButtonCTA();
|
|
@@ -109,7 +108,6 @@ export async function createButtonCTA(props: {
|
|
|
109
108
|
if (props.buttonVariant) button.buttonVariant = props.buttonVariant;
|
|
110
109
|
if (props.icon) button.icon = props.icon;
|
|
111
110
|
if (props.noWrap !== undefined) button.noWrap = props.noWrap;
|
|
112
|
-
if (props.disabled !== undefined) button.disabled = props.disabled;
|
|
113
111
|
if (props.testID) button.testID = props.testID;
|
|
114
112
|
|
|
115
113
|
// Append to document to trigger rendering
|