Files
bitvid/TEST_LOG.md
T
google-labs-jules[bot]andPR0M3TH3AN 21a7b43e80 chore(deps): bump esbuild to v0.27.3
- Upgraded esbuild from 0.27.2 to 0.27.3
- Added scripts/agent/analyze_deps.py
- Added artifacts/deps-report.md, npm-audit.json, npm-outdated.json
- Verified with npm run build, lint, and test:unit

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-12 14:39:37 +00:00

266 KiB

Node: v22.22.0 NPM: 11.7.0

added 424 packages, and audited 425 packages in 13s

108 packages are looking for funding run npm fund for details

found 0 vulnerabilities npm notice npm notice New minor version of npm available! 11.7.0 -> 11.10.0 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.10.0 npm notice To update run: npm install -g [email protected].0 npm notice

changed 2 packages, and audited 425 packages in 3s

108 packages are looking for funding run npm fund for details

found 0 vulnerabilities Running build...

[email protected] build node scripts/build-dist.mjs && npm run verify:dist:deploy-artifact

Validating service worker compatibility guard...

[email protected] lint:sw-compat node scripts/check-sw-compat.mjs

[sw-compat] Skipping check: unable to determine HEAD^ or git history unavailable. [sw-compat] No commit delta found; skipping compatibility check. Cleaning dist... Running build:css...

[email protected] build:css postcss css/tailwind.source.css -o css/tailwind.generated.css

Copying files... Copying directories... Generating hashed asset manifest... Rewriting HTML entry points with hashed assets... Injecting version hash and date... Injected version: 6bb2d251 • 2026-02-12 Build complete.

[email protected] verify:dist:deploy-artifact node scripts/verify-dist-deploy-artifact.mjs

Deployment artifact verification passed: version markup and hashed asset references are present in dist/index.html. Build exit code: 0 Running lint...

[email protected] lint npm run lint:css && npm run lint:hex && npm run lint:inline-styles && npm run lint:tokens && npm run lint:tailwind-brackets && npm run lint:tailwind-colors && npm run lint:file-size && npm run lint:innerhtml && npm run lint:assets && npm run lint:sw-compat

[email protected] lint:css stylelint css/tailwind.source.css css/tokens.css css/docs.css

[email protected] lint:hex node scripts/check-hex.js

[email protected] lint:inline-styles node scripts/check-inline-styles.mjs

No inline style usage found.

[email protected] lint:tokens node scripts/check-design-tokens.mjs --check=tokens

[email protected] lint:tailwind-brackets node scripts/check-design-tokens.mjs --check=brackets

[email protected] lint:tailwind-colors node scripts/check-tailwind-colors.mjs

[email protected] lint:file-size node scripts/check-file-size.mjs

[email protected] lint:innerhtml node scripts/check-innerhtml.mjs

[email protected] lint:assets node scripts/check-asset-references.mjs

Asset reference lint passed: all local CSS/JS references are hashed or allowlisted, and no ?v= query cache-busting remains.

[email protected] lint:sw-compat node scripts/check-sw-compat.mjs

[sw-compat] Skipping check: unable to determine HEAD^ or git history unavailable. [sw-compat] No commit delta found; skipping compatibility check. Lint exit code: 0 Running unit tests...

[email protected] test:unit node scripts/run-unit-tests.mjs

→ Running tests/app-batch-fetch-profiles.test.mjs TAP version 13

Subtest: batchFetchProfiles handles fast and failing relays

ok 1 - batchFetchProfiles handles fast and failing relays

duration_ms: 13.99331 type: 'test' ...

Subtest: batchFetchProfiles respects forceRefresh

ok 2 - batchFetchProfiles respects forceRefresh

duration_ms: 302.053628 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 365.991806

→ Running tests/app-state.test.mjs TAP version 13

Subtest: AppState

# Subtest: Initial state is clean
ok 1 - Initial state is clean
  ---
  duration_ms: 2.312659
  type: 'test'
  ...
# Subtest: setPubkey() updates state and notifies subscribers
ok 2 - setPubkey() updates state and notifies subscribers
  ---
  duration_ms: 0.944413
  type: 'test'
  ...
# Subtest: setCurrentUserNpub() updates state
ok 3 - setCurrentUserNpub() updates state
  ---
  duration_ms: 0.396352
  type: 'test'
  ...
# Subtest: setCurrentVideo() updates state
ok 4 - setCurrentVideo() updates state
  ---
  duration_ms: 0.571016
  type: 'test'
  ...
# Subtest: setVideosMap() updates state
ok 5 - setVideosMap() updates state
  ---
  duration_ms: 0.585743
  type: 'test'
  ...
# Subtest: setVideoSubscription() updates state
ok 6 - setVideoSubscription() updates state
  ---
  duration_ms: 0.330109
  type: 'test'
  ...
# Subtest: setModalState() updates state and notifies modal subscribers
ok 7 - setModalState() updates state and notifies modal subscribers
  ---
  duration_ms: 0.894319
  type: 'test'
  ...
# Subtest: Global subscriber receives updates
ok 8 - Global subscriber receives updates
  ---
  duration_ms: 0.626902
  type: 'test'
  ...
# Subtest: resetAppState() clears all state and notifies
ok 9 - resetAppState() clears all state and notifies
  ---
  duration_ms: 0.838283
  type: 'test'
  ...
1..9

ok 1 - AppState

duration_ms: 9.802838 type: 'suite' ... 1..1

tests 9

suites 1

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.18024

→ Running tests/app/channel-profile-moderation.test.mjs TAP version 13

Subtest: renderChannelVideosFromList decorates videos before storing

ok 1 - renderChannelVideosFromList decorates videos before storing

duration_ms: 185.777956 type: 'test' ...

Subtest: renderChannelVideosFromList applies moderation blur without existing metadata

ok 2 - renderChannelVideosFromList applies moderation blur without existing metadata

duration_ms: 58.613302 type: 'test' ...

Subtest: applyChannelVisualBlur blurs banner and avatar when viewer mutes author

ok 3 - applyChannelVisualBlur blurs banner and avatar when viewer mutes author

duration_ms: 15.849535 type: 'test' ...

Subtest: moderation override clears channel blur via event wiring

ok 4 - moderation override clears channel blur via event wiring

duration_ms: 36.515733 type: 'test' ...

Subtest: channel header moderation badge reflects blur state and override actions

ok 5 - channel header moderation badge reflects blur state and override actions

duration_ms: 47.930491 type: 'test' ...

Subtest: channel video cards update moderation state in place when summary changes

ok 6 - channel video cards update moderation state in place when summary changes

duration_ms: 48.058873 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 413.300296

→ Running tests/app/feedCoordinator.test.mjs TAP version 13

Subtest: createFeedCoordinator - loadForYouVideos

# Subtest: loadForYouVideos executes successfully
ok 1 - loadForYouVideos executes successfully
  ---
  duration_ms: 2.465866
  type: 'test'
  ...
1..1

ok 1 - createFeedCoordinator - loadForYouVideos

duration_ms: 4.55921 type: 'test' ... 1..1

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.21591

→ Running tests/app/hash-change-handler.test.mjs TAP version 13

Subtest: handleHashChange defaults to for-you when logged in

ok 1 - handleHashChange defaults to for-you when logged in

duration_ms: 240.819883 type: 'test' ...

Subtest: handleHashChange defaults to most-recent-videos when logged out

ok 2 - handleHashChange defaults to most-recent-videos when logged out

duration_ms: 20.042627 type: 'test' ...

Subtest: handleHashChange respects explicit view regardless of login state

ok 3 - handleHashChange respects explicit view regardless of login state

duration_ms: 12.440924 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 286.861711

→ Running tests/app/hydrate-sidebar-navigation.test.mjs TAP version 13

Subtest: hydrateSidebarNavigation reveals chrome controls for authenticated viewers

ok 1 - hydrateSidebarNavigation reveals chrome controls for authenticated viewers

duration_ms: 107.904014 type: 'test' ...

Subtest: hydrateSidebarNavigation hides chrome controls for logged-out viewers

ok 2 - hydrateSidebarNavigation hides chrome controls for logged-out viewers

duration_ms: 14.819112 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 135.542735

→ Running tests/app/is-user-logged-in.test.mjs TAP version 13

Subtest: isUserLoggedIn returns false when no user pubkey is set

ok 1 - isUserLoggedIn returns false when no user pubkey is set

duration_ms: 1.40207 type: 'test' ...

Subtest: isUserLoggedIn treats extension logins as authenticated

ok 2 - isUserLoggedIn treats extension logins as authenticated

duration_ms: 0.332666 type: 'test' ...

Subtest: isUserLoggedIn guards against mismatched nostrClient state

ok 3 - isUserLoggedIn guards against mismatched nostrClient state

duration_ms: 0.222126 type: 'test' ...

Subtest: isUserLoggedIn ignores anonymous session actor mismatches

ok 4 - isUserLoggedIn ignores anonymous session actor mismatches

duration_ms: 0.300322 type: 'test' ...

Subtest: isUserLoggedIn rejects mismatched managed session actors

ok 5 - isUserLoggedIn rejects mismatched managed session actors

duration_ms: 0.330816 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.161274

→ Running tests/app/moderation-overrides.test.mjs TAP version 13

Subtest: handleModerationOverride decorates stored and current videos then refreshes UI

ok 1 - handleModerationOverride decorates stored and current videos then refreshes UI

duration_ms: 5.979729 type: 'test' ...

Subtest: handleModerationOverride resumes deferred playback

ok 2 - handleModerationOverride resumes deferred playback

duration_ms: 1.337565 type: 'test' ...

Subtest: handleModerationBlock requests a block, clears overrides, and refreshes hidden state

ok 3 - handleModerationBlock requests a block, clears overrides, and refreshes hidden state

duration_ms: 4.307154 type: 'test' ...

Subtest: handleModerationBlock returns false when viewer is logged out

ok 4 - handleModerationBlock returns false when viewer is logged out

duration_ms: 1.098202 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.643321

→ Running tests/app/moderation-settings-refresh.test.mjs TAP version 13

Subtest: handleModerationSettingsChange refreshes feeds with updated thresholds

ok 1 - handleModerationSettingsChange refreshes feeds with updated thresholds

duration_ms: 5.049885 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.905125

→ Running tests/app/similar-content.test.mjs TAP version 13

Subtest: orders similar videos by shared tags then timestamp

ok 1 - orders similar videos by shared tags then timestamp

duration_ms: 3.272602 type: 'test' ...

Subtest: returns no matches when the active video lacks tags

ok 2 - returns no matches when the active video lacks tags

duration_ms: 0.456058 type: 'test' ...

Subtest: skips candidates without tag metadata

ok 3 - skips candidates without tag metadata

duration_ms: 0.408095 type: 'test' ...

Subtest: filters NSFW and private videos when NSFW content is disabled

ok 4 - filters NSFW and private videos when NSFW content is disabled

duration_ms: 0.535901 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.522938

→ Running tests/auth/signingAdapter.test.mjs TAP version 13

Subtest: createNip07SigningAdapter

# Subtest: uses explicit extension if provided
ok 1 - uses explicit extension if provided
  ---
  duration_ms: 3.540537
  type: 'test'
  ...
# Subtest: falls back to window.nostr if no extension provided
ok 2 - falls back to window.nostr if no extension provided
  ---
  duration_ms: 0.588977
  type: 'test'
  ...
# Subtest: throws error if no extension available
ok 3 - throws error if no extension available
  ---
  duration_ms: 1.377312
  type: 'test'
  ...
# Subtest: prioritizes explicit extension over window.nostr
ok 4 - prioritizes explicit extension over window.nostr
  ---
  duration_ms: 0.857442
  type: 'test'
  ...
1..4

ok 1 - createNip07SigningAdapter

duration_ms: 7.997828 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.053227

→ Running tests/comment-event-builder.test.mjs TAP version 13

Subtest: buildCommentEvent emits NIP-22 root metadata while keeping legacy fallbacks

ok 1 - buildCommentEvent emits NIP-22 root metadata while keeping legacy fallbacks

duration_ms: 2.793912 type: 'test' ...

Subtest: buildCommentEvent includes parent pointers, kinds, and authors for replies

ok 2 - buildCommentEvent includes parent pointers, kinds, and authors for replies

duration_ms: 0.594595 type: 'test' ...

Subtest: buildCommentEvent normalizes relays and preserves explicit overrides

ok 3 - buildCommentEvent normalizes relays and preserves explicit overrides

duration_ms: 0.820781 type: 'test' ...

Subtest: buildCommentEvent falls back to event pointers when no address is supplied

ok 4 - buildCommentEvent falls back to event pointers when no address is supplied

duration_ms: 0.289549 type: 'test' ...

Subtest: buildCommentEvent accepts partial metadata for parent overrides

ok 5 - buildCommentEvent accepts partial metadata for parent overrides

duration_ms: 0.365041 type: 'test' ...

Subtest: buildCommentEvent sanitizes additional tags before signing

ok 6 - buildCommentEvent sanitizes additional tags before signing

duration_ms: 0.873079 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.584239

→ Running tests/comment-thread-service.test.mjs TAP version 13

Subtest: CommentThreadService caches mixed-case video ids consistently

ok 1 - CommentThreadService caches mixed-case video ids consistently

duration_ms: 3.501828 type: 'test' ...

Subtest: CommentThreadService surfaces cache read failures

ok 2 - CommentThreadService surfaces cache read failures

duration_ms: 0.878062 type: 'test' ...

Subtest: CommentThreadService surfaces cache write failures

ok 3 - CommentThreadService surfaces cache write failures

duration_ms: 0.540859 type: 'test' ...

Subtest: CommentThreadService persists caches safely during teardown failures

ok 4 - CommentThreadService persists caches safely during teardown failures

duration_ms: 2.005017 type: 'test' ...

Subtest: CommentThreadService logs cache usage and fallback decisions

ok 5 - CommentThreadService logs cache usage and fallback decisions

duration_ms: 1.21468 type: 'test' ...

Subtest: CommentThreadService normalizes mixed-case event ids in thread state

ok 6 - CommentThreadService normalizes mixed-case event ids in thread state

duration_ms: 2.198276 type: 'test' ...

Subtest: CommentThreadService normalizes mixed-case pubkeys during hydration

ok 7 - CommentThreadService normalizes mixed-case pubkeys during hydration

duration_ms: 1.038858 type: 'test' ...

Subtest: CommentThreadService deduplicates mixed-case event ids and pubkeys

ok 8 - CommentThreadService deduplicates mixed-case event ids and pubkeys

duration_ms: 0.927499 type: 'test' ...

Subtest: CommentThreadService retries profile hydration before succeeding

ok 9 - CommentThreadService retries profile hydration before succeeding

duration_ms: 76.060413 type: 'test' ...

Subtest: CommentThreadService surfaces profile hydration failures after retries

ok 10 - CommentThreadService surfaces profile hydration failures after retries

duration_ms: 201.425935 type: 'test' ...

Subtest: listVideoComments accepts builder events without parent ids and filters replies

ok 11 - listVideoComments accepts builder events without parent ids and filters replies

duration_ms: 6.1484 type: 'test' ...

Subtest: CommentThreadService hydrates, subscribes, and dedupes incoming comment events

ok 12 - CommentThreadService hydrates, subscribes, and dedupes incoming comment events

duration_ms: 34.199346 type: 'test' ...

Subtest: CommentThreadService loadThread falls back to event id when address is missing

ok 13 - CommentThreadService loadThread falls back to event id when address is missing

duration_ms: 0.966091 type: 'test' ...

Subtest: CommentThreadService requests and snapshots comments by root identifier

ok 14 - CommentThreadService requests and snapshots comments by root identifier

duration_ms: 0.803819 type: 'test' ...

Subtest: CommentThreadService falls back to pointerIdentifiers root id

ok 15 - CommentThreadService falls back to pointerIdentifiers root id

duration_ms: 0.784538 type: 'test' ...

Subtest: CommentThreadService preserves raw video author pubkeys during hydration fetches

ok 16 - CommentThreadService preserves raw video author pubkeys during hydration fetches

duration_ms: 5.31487 type: 'test' ...

Subtest: CommentThreadService teardown cancels hydration timers

ok 17 - CommentThreadService teardown cancels hydration timers

duration_ms: 81.386057 type: 'test' ... 1..17

tests 17

suites 0

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 428.967471

→ Running tests/compliance/nip04_44_compliance.test.mjs TAP version 13

Subtest: NIP-04/44 Compliance: Encryption Preference

# Subtest: createNip46Cipher prefers nip44.v2 when available
ok 1 - createNip46Cipher prefers nip44.v2 when available
  ---
  duration_ms: 21.960504
  type: 'test'
  ...
# Subtest: createNip46Cipher falls back to nip04 if nip44 is missing
ok 2 - createNip46Cipher falls back to nip04 if nip44 is missing
  ---
  duration_ms: 9.44152
  type: 'test'
  ...
1..2

ok 1 - NIP-04/44 Compliance: Encryption Preference

duration_ms: 105.292078 type: 'test' ...

Subtest: NIP-04/44 Compliance: Decryption Fallback

# Subtest: decryptNip46PayloadWithKeys handles nip44 (v2) payload
ok 1 - decryptNip46PayloadWithKeys handles nip44 (v2) payload
  ---
  duration_ms: 24.872325
  type: 'test'
  ...
# Subtest: decryptNip46PayloadWithKeys handles nip04 payload
ok 2 - decryptNip46PayloadWithKeys handles nip04 payload
  ---
  duration_ms: 29.550968
  type: 'test'
  ...
1..2

ok 2 - NIP-04/44 Compliance: Decryption Fallback

duration_ms: 56.656775 type: 'test' ... 1..2

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 178.623688

→ Running tests/compliance/nip07_compliance.test.mjs TAP version 13

Subtest: NIP-07 Compliance: Retry Logic

# Subtest: runNip07WithRetry succeeds on first try
ok 1 - runNip07WithRetry succeeds on first try
  ---
  duration_ms: 2.056754
  type: 'test'
  ...
# Subtest: runNip07WithRetry retries on timeout error
ok 2 - runNip07WithRetry retries on timeout error
  ---
  duration_ms: 21.14576
  type: 'test'
  ...
# Subtest: runNip07WithRetry fails after max retries or if error is not timeout
ok 3 - runNip07WithRetry fails after max retries or if error is not timeout
  ---
  duration_ms: 1.443995
  type: 'test'
  ...
1..3

ok 1 - NIP-07 Compliance: Retry Logic

duration_ms: 26.423272 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 60.90597

→ Running tests/compliance/nip65_compliance.test.mjs TAP version 13

Subtest: NIP-65 Compliance: Relay List Loading

# Subtest: loadRelayList requests Kind 10002
ok 1 - loadRelayList requests Kind 10002
  ---
  duration_ms: 4.689822
  type: 'test'
  ...
# Subtest: loadRelayList parses r tags correctly
ok 2 - loadRelayList parses r tags correctly
  ---
  duration_ms: 0.42635
  type: 'test'
  ...
1..2

ok 1 - NIP-65 Compliance: Relay List Loading

duration_ms: 153.095055 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 167.16601

→ Running tests/compliance/video_note_compliance.test.mjs TAP version 13

Subtest: Video Note Compliance (Kind 30078 & NIP-71)

# Subtest: prepareVideoPublishPayload creates Kind 30078 event
ok 1 - prepareVideoPublishPayload creates Kind 30078 event
  ---
  duration_ms: 2.510144
  type: 'test'
  ...
# Subtest: prepareVideoPublishPayload includes NIP-71 tags when provided
ok 2 - prepareVideoPublishPayload includes NIP-71 tags when provided
  ---
  duration_ms: 1.061775
  type: 'test'
  ...
1..2

ok 1 - Video Note Compliance (Kind 30078 & NIP-71)

duration_ms: 36.920137 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 48.6723

→ Running tests/design-system-metrics.test.mjs TAP version 13

Subtest: metric probe falls back to numeric parsing when measurement fails

ok 1 - metric probe falls back to numeric parsing when measurement fails

duration_ms: 262.474863 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 273.524634

→ Running tests/designSystem/dynamicStyles.test.mjs TAP version 13

Subtest: js/designSystem/dynamicStyles.js

# Subtest: registerScope
    # Subtest: should register a scope and return a string ID
    ok 1 - should register a scope and return a string ID
      ---
      duration_ms: 94.693189
      type: 'test'
      ...
    # Subtest: should create a style element or use adoptedStyleSheets
    ok 2 - should create a style element or use adoptedStyleSheets
      ---
      duration_ms: 14.939194
      type: 'test'
      ...
    # Subtest: should insert rules with the correct selector
    ok 3 - should insert rules with the correct selector
      ---
      duration_ms: 13.524579
      type: 'test'
      ...
    # Subtest: should handle "&" in selectors
    ok 4 - should handle "&" in selectors
      ---
      duration_ms: 11.046488
      type: 'test'
      ...
    1..4
ok 1 - registerScope
  ---
  duration_ms: 135.426176
  type: 'suite'
  ...
# Subtest: setVariables
    # Subtest: should update CSS variables on the scope
    ok 1 - should update CSS variables on the scope
      ---
      duration_ms: 12.693568
      type: 'test'
      ...
    # Subtest: should handle removing variables
    ok 2 - should handle removing variables
      ---
      duration_ms: 9.815112
      type: 'test'
      ...
    1..2
ok 2 - setVariables
  ---
  duration_ms: 22.885703
  type: 'suite'
  ...
# Subtest: releaseScope
    # Subtest: should remove rules and clean up
    ok 1 - should remove rules and clean up
      ---
      duration_ms: 10.257187
      type: 'test'
      ...
    # Subtest: should return false for non-existent scope
    ok 2 - should return false for non-existent scope
      ---
      duration_ms: 13.536061
      type: 'test'
      ...
    1..2
ok 3 - releaseScope
  ---
  duration_ms: 24.195856
  type: 'suite'
  ...
# Subtest: Isolation
    # Subtest: should manage scopes independently for different documents
    ok 1 - should manage scopes independently for different documents
      ---
      duration_ms: 27.669183
      type: 'test'
      ...
    1..1
ok 4 - Isolation
  ---
  duration_ms: 27.795493
  type: 'suite'
  ...
# Subtest: Edge cases
    # Subtest: should handle invalid selectors gracefully
    ok 1 - should handle invalid selectors gracefully
      ---
      duration_ms: 9.567256
      type: 'test'
      ...
    # Subtest: should handle missing document gracefully
    ok 2 - should handle missing document gracefully
      ---
      duration_ms: 6.555677
      type: 'test'
      ...
    # Subtest: should fallback to <style> element if CSSStyleSheet is missing
    ok 3 - should fallback to <style> element if CSSStyleSheet is missing
      ---
      duration_ms: 13.983164
      type: 'test'
      ...
    1..3
ok 5 - Edge cases
  ---
  duration_ms: 30.574232
  type: 'suite'
  ...
1..5

ok 1 - js/designSystem/dynamicStyles.js

duration_ms: 241.993348 type: 'suite' ... 1..1

tests 12

suites 6

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 271.805236

→ Running tests/dm-block-filter.test.mjs TAP version 13

Subtest: DM block/mute filtering

# Subtest: setDmBlockChecker
    # Subtest: should accept a function
    ok 1 - should accept a function
      ---
      duration_ms: 1.600973
      type: 'test'
      ...
    # Subtest: should clear checker when passed null
    ok 2 - should clear checker when passed null
      ---
      duration_ms: 0.479151
      type: 'test'
      ...
    # Subtest: should clear checker when passed non-function
    ok 3 - should clear checker when passed non-function
      ---
      duration_ms: 0.712494
      type: 'test'
      ...
    1..3
ok 1 - setDmBlockChecker
  ---
  duration_ms: 4.355373
  type: 'suite'
  ...
# Subtest: applyDirectMessage with block checker
    # Subtest: should drop messages from blocked senders
    ok 1 - should drop messages from blocked senders
      ---
      duration_ms: 1.157129
      type: 'test'
      ...
    # Subtest: should allow messages from non-blocked senders
    ok 2 - should allow messages from non-blocked senders
      ---
      duration_ms: 3.397513
      type: 'test'
      ...
    # Subtest: should allow all messages when no block checker is set
    ok 3 - should allow all messages when no block checker is set
      ---
      duration_ms: 0.681846
      type: 'test'
      ...
    # Subtest: should block outgoing messages to blocked recipients
    ok 4 - should block outgoing messages to blocked recipients
      ---
      duration_ms: 0.502305
      type: 'test'
      ...
    1..4
ok 2 - applyDirectMessage with block checker
  ---
  duration_ms: 6.401836
  type: 'suite'
  ...
# Subtest: _isDmRemoteBlocked
    # Subtest: should return false when no checker is set
    ok 1 - should return false when no checker is set
      ---
      duration_ms: 0.533343
      type: 'test'
      ...
    # Subtest: should return true for blocked remote pubkey
    ok 2 - should return true for blocked remote pubkey
      ---
      duration_ms: 0.615485
      type: 'test'
      ...
    # Subtest: should return false for allowed remote pubkey
    ok 3 - should return false for allowed remote pubkey
      ---
      duration_ms: 0.453136
      type: 'test'
      ...
    # Subtest: should handle checker that throws
    ok 4 - should handle checker that throws
      ---
      duration_ms: 0.429247
      type: 'test'
      ...
    1..4
ok 3 - _isDmRemoteBlocked
  ---
  duration_ms: 2.355257
  type: 'suite'
  ...
1..3

ok 1 - DM block/mute filtering

duration_ms: 14.009526 type: 'suite' ... 1..1

tests 11

suites 4

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.60944

→ Running tests/dm-db.test.mjs TAP version 13

Subtest: writeMessages normalizes records before storing

ok 1 - writeMessages normalizes records before storing

duration_ms: 19.066028 type: 'test' ...

Subtest: updateConversationFromMessage persists metadata updates

ok 2 - updateConversationFromMessage persists metadata updates

duration_ms: 6.80147 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.854219

→ Running tests/dm-decryptor.test.mjs TAP version 13

Subtest: decryptDM handles kind 4 events with nip04 ciphertext

ok 1 - decryptDM handles kind 4 events with nip04 ciphertext

duration_ms: 82.944295 type: 'test' ...

Subtest: decryptDM prefers recipient pubkeys when actor is the sender

ok 2 - decryptDM prefers recipient pubkeys when actor is the sender

duration_ms: 26.96047 type: 'test' ...

Subtest: decryptDM unwraps kind 1059 gift wraps with nip44

ok 3 - decryptDM unwraps kind 1059 gift wraps with nip44

duration_ms: 58.695332 type: 'test' ...

Subtest: decryptDM returns failure when decryptors are unavailable

ok 4 - decryptDM returns failure when decryptors are unavailable

duration_ms: 0.368181 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 182.31751

→ Running tests/dm-normalization.test.mjs TAP version 13

Subtest: direct message normalization hashes conversations consistently

ok 1 - direct message normalization hashes conversations consistently

duration_ms: 17.675794 type: 'test' ...

Subtest: direct message list dedupes entries by event id

ok 2 - direct message list dedupes entries by event id

duration_ms: 6.486956 type: 'test' ...

→ Running tests/docs/verify-upload-claims.test.mjs TAP version 13

Subtest: Documentation Accuracy Verification

# Subtest: setup
ok 1 - setup
  ---
  duration_ms: 8.134501
  type: 'test'
  ...
# Subtest: should list accepted video file extensions in docs matching the HTML accept attribute
ok 2 - should list accepted video file extensions in docs matching the HTML accept attribute
  ---
  duration_ms: 0.676435
  type: 'test'
  ...
# Subtest: should state Title is required in docs and be required in HTML
ok 3 - should state Title is required in docs and be required in HTML
  ---
  duration_ms: 0.388567
  type: 'test'
  ...
# Subtest: should mention 2GB limit recommendation in docs and HTML
ok 4 - should mention 2GB limit recommendation in docs and HTML
  ---
  duration_ms: 0.381846
  type: 'test'
  ...
1..4

ok 1 - Documentation Accuracy Verification

duration_ms: 11.307883 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.603302

→ Running tests/edit-modal-submit-state.test.mjs TAP version 13

Subtest: ignores additional submissions while pending without spurious errors

ok 1 - ignores additional submissions while pending without spurious errors

duration_ms: 281.57233 type: 'test' ...

Subtest: editing the magnet refreshes torrent hints when ws/xs remain locked

ok 2 - editing the magnet refreshes torrent hints when ws/xs remain locked

duration_ms: 105.743501 type: 'test' ...

Subtest: does not show missing video error after modal closes

ok 3 - does not show missing video error after modal closes

duration_ms: 93.119821 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 493.900317

→ Running tests/event-schemas.test.mjs TAP version 13

Subtest: Nostr Event Schemas

# Subtest: should validate buildVideoPostEvent
ok 1 - should validate buildVideoPostEvent
  ---
  duration_ms: 2.366086
  type: 'test'
  ...
# Subtest: should validate buildVideoMirrorEvent
ok 2 - should validate buildVideoMirrorEvent
  ---
  duration_ms: 0.412911
  type: 'test'
  ...
# Subtest: should validate buildRepostEvent
ok 3 - should validate buildRepostEvent
  ---
  duration_ms: 0.496216
  type: 'test'
  ...
# Subtest: should validate buildShareEvent
ok 4 - should validate buildShareEvent
  ---
  duration_ms: 0.752307
  type: 'test'
  ...
# Subtest: should validate buildRelayListEvent
ok 5 - should validate buildRelayListEvent
  ---
  duration_ms: 0.547462
  type: 'test'
  ...
# Subtest: should validate buildDmRelayListEvent
ok 6 - should validate buildDmRelayListEvent
  ---
  duration_ms: 0.494933
  type: 'test'
  ...
# Subtest: should validate buildProfileMetadataEvent
ok 7 - should validate buildProfileMetadataEvent
  ---
  duration_ms: 0.479381
  type: 'test'
  ...
# Subtest: should validate buildMuteListEvent
ok 8 - should validate buildMuteListEvent
  ---
  duration_ms: 0.414225
  type: 'test'
  ...
# Subtest: should validate buildDeletionEvent
ok 9 - should validate buildDeletionEvent
  ---
  duration_ms: 0.847797
  type: 'test'
  ...
# Subtest: should validate buildLegacyDirectMessageEvent
ok 10 - should validate buildLegacyDirectMessageEvent
  ---
  duration_ms: 0.504699
  type: 'test'
  ...
# Subtest: should validate buildDmAttachmentEvent
ok 11 - should validate buildDmAttachmentEvent
  ---
  duration_ms: 0.271428
  type: 'test'
  ...
# Subtest: should validate buildDmReadReceiptEvent
ok 12 - should validate buildDmReadReceiptEvent
  ---
  duration_ms: 0.246756
  type: 'test'
  ...
# Subtest: should validate buildDmTypingIndicatorEvent
ok 13 - should validate buildDmTypingIndicatorEvent
  ---
  duration_ms: 0.304958
  type: 'test'
  ...
# Subtest: should validate buildViewEvent
ok 14 - should validate buildViewEvent
  ---
  duration_ms: 0.533127
  type: 'test'
  ...
# Subtest: should validate buildZapRequestEvent
ok 15 - should validate buildZapRequestEvent
  ---
  duration_ms: 0.397321
  type: 'test'
  ...
# Subtest: should validate buildReactionEvent
ok 16 - should validate buildReactionEvent
  ---
  duration_ms: 0.720611
  type: 'test'
  ...
# Subtest: should validate buildCommentEvent
ok 17 - should validate buildCommentEvent
  ---
  duration_ms: 0.814599
  type: 'test'
  ...
# Subtest: should validate buildWatchHistoryEvent
ok 18 - should validate buildWatchHistoryEvent
  ---
  duration_ms: 0.36468
  type: 'test'
  ...
# Subtest: should validate buildSubscriptionListEvent
ok 19 - should validate buildSubscriptionListEvent
  ---
  duration_ms: 0.234519
  type: 'test'
  ...
# Subtest: should validate buildBlockListEvent
ok 20 - should validate buildBlockListEvent
  ---
  duration_ms: 0.18215
  type: 'test'
  ...
# Subtest: should validate buildHashtagPreferenceEvent
ok 21 - should validate buildHashtagPreferenceEvent
  ---
  duration_ms: 0.160192
  type: 'test'
  ...
# Subtest: should validate buildAdminListEvent (moderation)
ok 22 - should validate buildAdminListEvent (moderation)
  ---
  duration_ms: 0.336382
  type: 'test'
  ...
1..22

ok 1 - Nostr Event Schemas

duration_ms: 14.763646 type: 'suite' ... 1..1

tests 22

suites 1

pass 22

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 39.008293

→ Running tests/feed-engine/explore-diversity.test.mjs TAP version 13

Subtest: explore diversity sorter increases tag diversity in the top results

ok 1 - explore diversity sorter increases tag diversity in the top results

duration_ms: 2.740235 type: 'test' ...

Subtest: explore diversity sorter logs why when MMR re-orders similar candidates

ok 2 - explore diversity sorter logs why when MMR re-orders similar candidates

duration_ms: 1.246067 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.125799

→ Running tests/feed-engine/explore-scorer.test.mjs TAP version 13

Subtest: explore disinterest filter drops videos with disinterested tags

ok 1 - explore disinterest filter drops videos with disinterested tags

duration_ms: 1.835217 type: 'test' ...

Subtest: explore scorer penalizes disinterest overlap and logs why metadata

ok 2 - explore scorer penalizes disinterest overlap and logs why metadata

duration_ms: 1.846634 type: 'test' ...

Subtest: explore scorer rewards novelty and new tag fraction

ok 3 - explore scorer rewards novelty and new tag fraction

duration_ms: 0.550562 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.784106

→ Running tests/feed-engine/kids-scorer.test.mjs TAP version 13

Subtest: kids scorer correctly identifies dominant positive component

ok 1 - kids scorer correctly identifies dominant positive component

duration_ms: 2.685583 type: 'test' ...

Subtest: kids scorer picks educational-boost when dominant

ok 2 - kids scorer picks educational-boost when dominant

duration_ms: 0.482839 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.715563

→ Running tests/feed-engine/tag-preference-filter.test.mjs TAP version 13

Subtest: tag preference stage filters by interests and excludes disinterests

ok 1 - tag preference stage filters by interests and excludes disinterests

duration_ms: 2.86039 type: 'test' ...

Subtest: tag preference stage accepts interests from nip71 hashtags

ok 2 - tag preference stage accepts interests from nip71 hashtags

duration_ms: 0.411438 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.497135

→ Running tests/grid-health.test.mjs TAP version 13

Subtest: prioritizeEntries input validation

ok 1 - prioritizeEntries input validation

duration_ms: 85.707893 type: 'test' ...

Subtest: prioritizeEntries filtering

ok 2 - prioritizeEntries filtering

duration_ms: 12.942524 type: 'test' ...

Subtest: prioritizeEntries without viewport center

ok 3 - prioritizeEntries without viewport center

duration_ms: 9.9473 type: 'test' ...

Subtest: prioritizeEntries with viewport center

ok 4 - prioritizeEntries with viewport center

duration_ms: 9.807917 type: 'test' ...

Subtest: prioritizeEntries ratio prioritization when distances are similar

ok 5 - prioritizeEntries ratio prioritization when distances are similar

duration_ms: 10.32661 type: 'test' ...

Subtest: prioritizeEntries uses boundingClientRect fallback

ok 6 - prioritizeEntries uses boundingClientRect fallback

duration_ms: 10.271787 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 158.35628

→ Running tests/hashtag-preferences.test.mjs [bitvid] [HashtagPreferences] Failed to decrypt hashtag preferences Error: Decrypt permissions are required to read hashtag preferences. at HashtagPreferencesService.decryptEvent (file:///app/js/services/hashtagPreferencesService.js:1011:23) at async HashtagPreferencesService.loadInternal (file:///app/js/services/hashtagPreferencesService.js:854:25) at async HashtagPreferencesService.load (file:///app/js/services/hashtagPreferencesService.js:566:7) at async TestContext. (file:///app/tests/hashtag-preferences.test.mjs:291:7) at async Test.run (node:internal/test_runner/test:1054:7) at async Test.processPendingSubtests (node:internal/test_runner/test:744:7) { code: 'hashtag-preferences-permission-required' } TAP version 13

Subtest: load decrypts nip44 payloads and normalizes tags

ok 1 - load decrypts nip44 payloads and normalizes tags

duration_ms: 22.734977 type: 'test' ...

Subtest: load falls back to nip04 decryption

ok 2 - load falls back to nip04 decryption

duration_ms: 2.826757 type: 'test' ...

Subtest: load retries decryptors when hinted scheme fails

ok 3 - load retries decryptors when hinted scheme fails

duration_ms: 2.729368 type: 'test' ...

Subtest: load defers permission-required decrypts until explicitly enabled

ok 4 - load defers permission-required decrypts until explicitly enabled

duration_ms: 6.634545 type: 'test' ...

Subtest: interest and disinterest lists remain exclusive

ok 5 - interest and disinterest lists remain exclusive

duration_ms: 14.254661 type: 'test' ...

Subtest: publish encrypts payload and builds event via builder

ok 6 - publish encrypts payload and builds event via builder

duration_ms: 2.908514 type: 'test' ...

Subtest: publish falls back to window nostr encryptors

ok 7 - publish falls back to window nostr encryptors

duration_ms: 2.950692 type: 'test' ...

Subtest: load prefers canonical kind when timestamps match while accepting legacy payload

ok 8 - load prefers canonical kind when timestamps match while accepting legacy payload

duration_ms: 2.379647 type: 'test' ...

Subtest: load falls back to window.nostr when active signer is missing

ok 9 - load falls back to window.nostr when active signer is missing

duration_ms: 2.099082 type: 'test' ...

Subtest: load falls back to window.nostr when active signer lacks decrypt capabilities

ok 10 - load falls back to window.nostr when active signer lacks decrypt capabilities

duration_ms: 1.726472 type: 'test' ...

→ Running tests/login-modal-controller.test.mjs TAP version 13

Subtest: LoginModalController shows custom error for empty nsec input

ok 1 - LoginModalController shows custom error for empty nsec input

duration_ms: 212.73985 type: 'test' ...

Subtest: LoginModalController does not set required attribute when toggling modes

ok 2 - LoginModalController does not set required attribute when toggling modes

duration_ms: 60.904058 type: 'test' ...

Subtest: LoginModalController shows NIP-46 handshake panel

ok 3 - LoginModalController shows NIP-46 handshake panel

duration_ms: 54.632717 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 341.431434

→ Running tests/magnet-utils.test.mjs TAP version 13

Subtest: safeDecodeMagnet handles encoded values

ok 1 - safeDecodeMagnet handles encoded values

duration_ms: 1.830381 type: 'test' ...

Subtest: safeDecodeMagnet leaves plain strings untouched

ok 2 - safeDecodeMagnet leaves plain strings untouched

duration_ms: 0.257354 type: 'test' ...

Subtest: bare hashes normalize consistently

ok 3 - bare hashes normalize consistently

duration_ms: 2.815832 type: 'test' ...

Subtest: legacy %3A payloads decode

ok 4 - legacy %3A payloads decode

duration_ms: 0.614362 type: 'test' ...

Subtest: duplicate trackers and hints stay in sync

ok 5 - duplicate trackers and hints stay in sync

duration_ms: 0.775455 type: 'test' ...

Subtest: object helper enforces HTTPS web seeds when on HTTPS

ok 6 - object helper enforces HTTPS web seeds when on HTTPS

duration_ms: 0.439018 type: 'test' ...

Subtest: object helper allows HTTP seeds on HTTP origins

ok 7 - object helper allows HTTP seeds on HTTP origins

duration_ms: 0.456636 type: 'test' ...

Subtest: extractMagnetHints returns first ws/xs pair

ok 8 - extractMagnetHints returns first ws/xs pair

duration_ms: 0.536883 type: 'test' ...

Subtest: object helper reports didChange when output differs

ok 9 - object helper reports didChange when output differs

duration_ms: 0.753644 type: 'test' ...

Subtest: helpers trim fragments from non-magnet values

ok 10 - helpers trim fragments from non-magnet values

duration_ms: 0.605723 type: 'test' ...

Subtest: normalizeAndAugmentMagnet filters out known broken trackers

ok 11 - normalizeAndAugmentMagnet filters out known broken trackers

duration_ms: 0.595131 type: 'test' ...

Subtest: normalizeMagnetInput parses each inbound parameter exactly once

ok 12 - normalizeMagnetInput parses each inbound parameter exactly once

duration_ms: 0.346684 type: 'test' ...

Subtest: normalizeAndAugmentMagnet keeps unchanged xt/tr/ws/xs params singular

ok 13 - normalizeAndAugmentMagnet keeps unchanged xt/tr/ws/xs params singular

duration_ms: 0.556752 type: 'test' ...

Subtest: normalizeMagnetInput preserves parameter values containing additional equals signs

ok 14 - normalizeMagnetInput preserves parameter values containing additional equals signs

duration_ms: 0.413061 type: 'test' ... 1..14

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.632838

→ Running tests/media-loader.test.mjs TAP version 13

Subtest: MediaLoader assigns image sources once intersecting

ok 1 - MediaLoader assigns image sources once intersecting

duration_ms: 94.328746 type: 'test' ...

Subtest: MediaLoader loads video sources and poster fallbacks

ok 2 - MediaLoader loads video sources and poster fallbacks

duration_ms: 19.682415 type: 'test' ...

Subtest: MediaLoader clears unsupported lazy targets without inline styles

ok 3 - MediaLoader clears unsupported lazy targets without inline styles

duration_ms: 11.90463 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 140.751529

→ Running tests/minimal-channel-profile.test.mjs TAP version 13

Subtest: can import channelProfile

ok 1 - can import channelProfile

duration_ms: 0.947132 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.218447

→ Running tests/minimal-webtorrent.test.mjs TAP version 13

Subtest: can import WebTorrent

ok 1 - can import WebTorrent

duration_ms: 0.98591 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 10.966084

→ Running tests/modal-accessibility.test.mjs TAP version 13

Subtest: UploadModal closes on Escape and restores trigger focus

ok 1 - UploadModal closes on Escape and restores trigger focus

duration_ms: 320.735225 type: 'test' ...

Subtest: UploadModal backdrop click closes and restores trigger focus

ok 2 - UploadModal backdrop click closes and restores trigger focus

duration_ms: 84.805821 type: 'test' ...

Subtest: UploadModal mode toggle updates button states

ok 3 - UploadModal mode toggle updates button states

duration_ms: 72.765336 type: 'test' ...

Subtest: EditModal Escape closes and restores trigger focus

ok 4 - EditModal Escape closes and restores trigger focus

duration_ms: 95.217634 type: 'test' ...

Subtest: EditModal backdrop click closes and restores trigger focus

ok 5 - EditModal backdrop click closes and restores trigger focus

duration_ms: 88.289785 type: 'test' ...

Subtest: EditModal visibility toggle updates button state

ok 6 - EditModal visibility toggle updates button state

duration_ms: 80.063657 type: 'test' ...

Subtest: RevertModal Escape closes and restores trigger focus

ok 7 - RevertModal Escape closes and restores trigger focus

duration_ms: 59.22594 type: 'test' ...

Subtest: static modal helper toggles accessibility hooks

ok 8 - static modal helper toggles accessibility hooks

duration_ms: 13.01931 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 822.823162

→ Running tests/moderation-service.test.mjs TAP version 13

Subtest: trusted report summaries respect personal blocks and admin lists

ok 1 - trusted report summaries respect personal blocks and admin lists

duration_ms: 5.111963 type: 'test' ...

Subtest: user block updates recompute summaries and emit notifications

ok 2 - user block updates recompute summaries and emit notifications

duration_ms: 4.383673 type: 'test' ...

Subtest: moderation thresholds emit logger hooks only when crossing

ok 3 - moderation thresholds emit logger hooks only when crossing

duration_ms: 1.677059 type: 'test' ...

Subtest: trusted mute aggregation tracks F1 mute lists

ok 4 - trusted mute aggregation tracks F1 mute lists

duration_ms: 1.674698 type: 'test' ...

Subtest: viewer mute list publishes and updates aggregation

ok 5 - viewer mute list publishes and updates aggregation

duration_ms: 2.485103 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2022.619582

→ Running tests/moderation-stage.test.mjs TAP version 13

Subtest: moderation stage enforces admin lists without whitelist bypass

ok 1 - moderation stage enforces admin lists without whitelist bypass

duration_ms: 5.179233 type: 'test' ...

Subtest: moderation stage annotates trusted mute metadata

ok 2 - moderation stage annotates trusted mute metadata

duration_ms: 1.195483 type: 'test' ...

Subtest: moderation stage applies provided thresholds

ok 3 - moderation stage applies provided thresholds

duration_ms: 1.475407 type: 'test' ...

Subtest: moderation stage respects runtime threshold changes

ok 4 - moderation stage respects runtime threshold changes

duration_ms: 0.940511 type: 'test' ...

Subtest: moderation stage supports function-based threshold resolvers

ok 5 - moderation stage supports function-based threshold resolvers

duration_ms: 0.830756 type: 'test' ...

Subtest: moderation stage propagates whitelist, muters, and threshold updates

ok 6 - moderation stage propagates whitelist, muters, and threshold updates

duration_ms: 1.19115 type: 'test' ...

Subtest: moderation stage blurs viewer-muted authors

ok 7 - moderation stage blurs viewer-muted authors

duration_ms: 0.557561 type: 'test' ...

Subtest: moderation stage clears cached reporters and muters after service signals

ok 8 - moderation stage clears cached reporters and muters after service signals

duration_ms: 0.855191 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 36.940286

→ Running tests/moderation/hide-thresholds.test.mjs TAP version 13

Subtest: moderation stage hides videos muted by trusted when threshold met

ok 1 - moderation stage hides videos muted by trusted when threshold met

duration_ms: 4.518875 type: 'test' ...

Subtest: moderation stage hides videos when trusted reports exceed threshold

ok 2 - moderation stage hides videos when trusted reports exceed threshold

duration_ms: 0.717525 type: 'test' ...

Subtest: moderation stage respects runtime hide threshold changes

ok 3 - moderation stage respects runtime hide threshold changes

duration_ms: 1.097001 type: 'test' ...

Subtest: moderation stage supports trusted mute hide resolver functions

ok 4 - moderation stage supports trusted mute hide resolver functions

duration_ms: 1.162414 type: 'test' ...

Subtest: moderation stage bypasses hard hides on home feed

ok 5 - moderation stage bypasses hard hides on home feed

duration_ms: 0.66634 type: 'test' ...

Subtest: moderation stage hides admin-whitelisted videos once thresholds fire

ok 6 - moderation stage hides admin-whitelisted videos once thresholds fire

duration_ms: 0.721653 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.358557

→ Running tests/moderation/submit-report.test.mjs TAP version 13

Subtest: submitReport emits NIP-56 compliant report tags

ok 1 - submitReport emits NIP-56 compliant report tags

duration_ms: 11.180256 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.106344

→ Running tests/moderation/trust-seeds.test.mjs [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed TAP version 13

Subtest: default trust seeds derive from config

ok 1 - default trust seeds derive from config

duration_ms: 2.201595 type: 'test' ...

Subtest: trusted seeds contribute to trusted mute counts

ok 2 - trusted seeds contribute to trusted mute counts

duration_ms: 4.244108 type: 'test' ...

Subtest: trusted seeds contribute to trusted report counts

ok 3 - trusted seeds contribute to trusted report counts

duration_ms: 3.80645 type: 'test' ...

Subtest: trusted seed updates recompute guest report summaries

ok 4 - trusted seed updates recompute guest report summaries

duration_ms: 1.226695 type: 'test' ...

Subtest: moderator seeds populate trusted contacts

ok 5 - moderator seeds populate trusted contacts

duration_ms: 0.945059 type: 'test' ...

Subtest: bootstrap seeds track editor roster and ignore whitelist-only changes

ok 6 - bootstrap seeds track editor roster and ignore whitelist-only changes

duration_ms: 100.078365 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3620.078395

→ Running tests/moderation/trusted-mute-lists.test.mjs TAP version 13

Subtest: trusted mute lists from seeds hide authors for anonymous viewers

ok 1 - trusted mute lists from seeds hide authors for anonymous viewers

duration_ms: 15.996734 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2018.495766

→ Running tests/moderation/trusted-report-count.test.mjs [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed [bitvid] [moderationService] moderation threshold crossed TAP version 13

Subtest: trustedReportCount dedupes multiple reports from the same trusted reporter

ok 1 - trustedReportCount dedupes multiple reports from the same trusted reporter

duration_ms: 5.849074 type: 'test' ...

Subtest: trustedReportCount ignores reports from muted or blocked reporters

ok 2 - trustedReportCount ignores reports from muted or blocked reporters

duration_ms: 2.464226 type: 'test' ...

Subtest: blocking and unblocking reporters recomputes trusted summaries

ok 3 - blocking and unblocking reporters recomputes trusted summaries

duration_ms: 3.151037 type: 'test' ...

Subtest: trustedReportCount only counts eligible F1 reporters and admin whitelist

ok 4 - trustedReportCount only counts eligible F1 reporters and admin whitelist

duration_ms: 2.141379 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2019.16829

→ Running tests/moderation/video-card.test.mjs TAP version 13

Subtest: VideoCard renders moderation badges and respects viewer override

ok 1 - VideoCard renders moderation badges and respects viewer override

duration_ms: 139.596585 type: 'test' ...

Subtest: VideoCard hides content when hide metadata is present and override unmasks it

ok 2 - VideoCard hides content when hide metadata is present and override unmasks it

duration_ms: 34.510215 type: 'test' ...

Subtest: VideoCard blurs viewer-muted creators

ok 3 - VideoCard blurs viewer-muted creators

duration_ms: 19.793226 type: 'test' ...

Subtest: VideoCard blurs thumbnails when trusted mute triggers without reports

ok 4 - VideoCard blurs thumbnails when trusted mute triggers without reports

duration_ms: 17.103429 type: 'test' ...

Subtest: VideoCard block action restores trusted mute hide state after override

ok 5 - VideoCard block action restores trusted mute hide state after override

duration_ms: 31.874427 type: 'test' ...

Subtest: applyModerationContextDatasets clears blur when overrides are active

ok 6 - applyModerationContextDatasets clears blur when overrides are active

duration_ms: 9.328694 type: 'test' ...

Subtest: buildModerationBadgeText returns trusted contact block copy when autoplay block and trusted mute combine

ok 7 - buildModerationBadgeText returns trusted contact block copy when autoplay block and trusted mute combine

duration_ms: 0.266767 type: 'test' ... 1..7

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 269.269176

→ Running tests/more-menu-controller.test.mjs TAP version 13

Subtest: copy-link action writes to clipboard and shows success

duration_ms: 3.095662 type: 'test' ...

Subtest: blacklist-author requires moderator access and refreshes subscriptions

ok 2 - blacklist-author requires moderator access and refreshes subscriptions

duration_ms: 0.676319 type: 'test' ...

Subtest: blacklist-author shows error when no moderator session is available

ok 3 - blacklist-author shows error when no moderator session is available

duration_ms: 0.36867 type: 'test' ...

Subtest: block-author updates user blocks, reloads videos, and refreshes feeds

ok 4 - block-author updates user blocks, reloads videos, and refreshes feeds

duration_ms: 0.661365 type: 'test' ...

Subtest: mute-author updates viewer mute list and refreshes feeds

ok 5 - mute-author updates viewer mute list and refreshes feeds

duration_ms: 0.666003 type: 'test' ...

Subtest: unmute-author removes creators from viewer mute list

ok 6 - unmute-author removes creators from viewer mute list

duration_ms: 0.607944 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.398834

→ Running tests/nip07-concurrency.test.mjs TAP version 13

Subtest: NIP-07 Concurrency Queue

# Subtest: runs up to 5 tasks concurrently
ok 1 - runs up to 5 tasks concurrently
  ---
  duration_ms: 304.272785
  type: 'test'
  ...
# Subtest: respects priority
ok 2 - respects priority
  ---
  duration_ms: 153.423962
  type: 'test'
  ...
1..2

ok 1 - NIP-07 Concurrency Queue

duration_ms: 459.795455 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 467.779844

→ Running tests/nip71-base-event-tags.test.mjs TAP version 13

Subtest: 30078 events carry nip71 metadata tags and hydrate fallback metadata

ok 1 - 30078 events carry nip71 metadata tags and hydrate fallback metadata

duration_ms: 5.946745 type: 'test' ...

Subtest: imeta tags lower-case mime values

ok 2 - imeta tags lower-case mime values

duration_ms: 0.446174 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.964068

→ Running tests/nip71-builder.test.mjs TAP version 13

Subtest: buildNip71VideoEvent assembles rich metadata

ok 1 - buildNip71VideoEvent assembles rich metadata

duration_ms: 3.778872 type: 'test' ...

Subtest: buildNip71VideoEvent falls back to summary and selects kind

ok 2 - buildNip71VideoEvent falls back to summary and selects kind

duration_ms: 0.399633 type: 'test' ...

Subtest: buildNip71VideoEvent attaches pointer tags

ok 3 - buildNip71VideoEvent attaches pointer tags

duration_ms: 0.404645 type: 'test' ...

Subtest: extractNip71MetadataFromTags parses metadata and pointers

ok 4 - extractNip71MetadataFromTags parses metadata and pointers

duration_ms: 1.193632 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.218498

→ Running tests/nip71-form-manager.test.mjs TAP version 13

Subtest: collectSection sanitizes and deduplicates hashtags

ok 1 - collectSection sanitizes and deduplicates hashtags

duration_ms: 119.227299 type: 'test' ...

Subtest: hydrateSection renders sanitized hashtags with prefix

ok 2 - hydrateSection renders sanitized hashtags with prefix

duration_ms: 24.051715 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 160.203219

→ Running tests/nostr-login-permissions.test.mjs TAP version 13

Subtest: NIP-07 Login Permissions

# Subtest: NIP-07 login requests decrypt permissions upfront
ok 1 - NIP-07 login requests decrypt permissions upfront
  ---
  duration_ms: 5.74107
  type: 'test'
  ...
# Subtest: NIP-07 decrypt reuses cached extension permissions
ok 2 - NIP-07 decrypt reuses cached extension permissions
  ---
  duration_ms: 1.703473
  type: 'test'
  ...
# Subtest: NIP-07 login falls back when structured permissions fail
ok 3 - NIP-07 login falls back when structured permissions fail
  ---
  duration_ms: 1.548918
  type: 'test'
  ...
# Subtest: NIP-07 login supports extensions that only allow enable() without payload
ok 4 - NIP-07 login supports extensions that only allow enable() without payload
  ---
  duration_ms: 1.453856
  type: 'test'
  ...
# Subtest: NIP-07 login quickly retries when a permission payload stalls
ok 5 - NIP-07 login quickly retries when a permission payload stalls
  ---
  duration_ms: 82.700552
  type: 'test'
  ...
# Subtest: NIP-07 login does not wait for deferred permission grants
ok 6 - NIP-07 login does not wait for deferred permission grants
  ---
  duration_ms: 302.345147
  type: 'test'
  ...
# Subtest: NIP-07 login surfaces enable permission errors
ok 7 - NIP-07 login surfaces enable permission errors
  ---
  duration_ms: 1.709508
  type: 'test'
  ...
# Subtest: runNip07WithRetry respects timeout without retry multiplier
ok 8 - runNip07WithRetry respects timeout without retry multiplier
  ---
  duration_ms: 60.666658
  type: 'test'
  ...
1..8

ok 1 - NIP-07 Login Permissions

duration_ms: 460.039633 type: 'suite' ... 1..1

tests 8

suites 1

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 468.81276

→ Running tests/nostr-nip46-queue.test.mjs TAP version 13

Subtest: Nip46RequestQueue: processes tasks in FIFO order for same priority

ok 1 - Nip46RequestQueue: processes tasks in FIFO order for same priority

duration_ms: 2.352689 type: 'test' ...

Subtest: Nip46RequestQueue: respects priority levels

ok 2 - Nip46RequestQueue: respects priority levels

duration_ms: 10.56416 type: 'test' ...

Subtest: Nip46RequestQueue: respects minDelayMs

ok 3 - Nip46RequestQueue: respects minDelayMs

duration_ms: 101.61828 type: 'test' ...

Subtest: Nip46RequestQueue: clear() rejects pending tasks

ok 4 - Nip46RequestQueue: clear() rejects pending tasks

duration_ms: 101.564683 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 224.639682

→ Running tests/nostr-private-key-signer.test.mjs TAP version 13

Subtest: Nostr Private Key Signer

# Subtest: registerPrivateKeySigner exposes nip04 helpers
ok 1 - registerPrivateKeySigner exposes nip04 helpers
  ---
  duration_ms: 150.613689
  type: 'test'
  ...
1..1

ok 1 - Nostr Private Key Signer

duration_ms: 152.22873 type: 'suite' ...

→ Running tests/nostr-send-direct-message.test.mjs TAP version 13

Subtest: sendDirectMessage succeeds with private key signer and no extension

ok 1 - sendDirectMessage succeeds with private key signer and no extension

duration_ms: 228.703559 type: 'test' ...

→ Running tests/nostr-service-access-control.test.mjs TAP version 13

Subtest: shouldIncludeVideo returns true for whitelist author when npubEncode throws

ok 1 - shouldIncludeVideo returns true for whitelist author when npubEncode throws

duration_ms: 2.87947 type: 'test' ...

Subtest: shouldIncludeVideo rejects blacklisted authors provided as npub

ok 2 - shouldIncludeVideo rejects blacklisted authors provided as npub

duration_ms: 2.518078 type: 'test' ...

Subtest: shouldIncludeVideo rejects blacklisted authors provided as hex when npubEncode throws

ok 3 - shouldIncludeVideo rejects blacklisted authors provided as hex when npubEncode throws

duration_ms: 63.943386 type: 'test' ...

Subtest: shouldIncludeVideo always returns true for the viewer's own video

ok 4 - shouldIncludeVideo always returns true for the viewer's own video

duration_ms: 0.386115 type: 'test' ...

Subtest: shouldIncludeVideo allows access when access control would deny the author

ok 5 - shouldIncludeVideo allows access when access control would deny the author

duration_ms: 0.387781 type: 'test' ...

→ Running tests/nostr-session-actor.test.mjs TAP version 13

Subtest: isSubtleCryptoAvailable returns true in test env

ok 1 - isSubtleCryptoAvailable returns true in test env

duration_ms: 2.057977 type: 'test' ...

Subtest: generateRandomBytes returns correct length

ok 2 - generateRandomBytes returns correct length

duration_ms: 1.688036 type: 'test' ...

Subtest: encryptSessionPrivateKey and decryptSessionPrivateKey roundtrip

ok 3 - encryptSessionPrivateKey and decryptSessionPrivateKey roundtrip

duration_ms: 300.323252 type: 'test' ...

Subtest: decryptSessionPrivateKey fails with wrong passphrase

ok 4 - decryptSessionPrivateKey fails with wrong passphrase

duration_ms: 311.777993 type: 'test' ...

Subtest: persistSessionActor writes to localStorage and IndexedDB

ok 5 - persistSessionActor writes to localStorage and IndexedDB

duration_ms: 153.639971 type: 'test' ...

Subtest: readStoredSessionActorEntry retrieves from localStorage

ok 6 - readStoredSessionActorEntry retrieves from localStorage

duration_ms: 152.07417 type: 'test' ...

Subtest: clearStoredSessionActor removes from localStorage

ok 7 - clearStoredSessionActor removes from localStorage

duration_ms: 1.510781 type: 'test' ...

Subtest: helper: arrayBufferToBase64 and base64ToUint8Array roundtrip

ok 8 - helper: arrayBufferToBase64 and base64ToUint8Array roundtrip

duration_ms: 2.535837 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 935.39355

→ Running tests/nostr-signer-race.test.mjs TAP version 13

Subtest: ensureActiveSignerForPubkey prefers concurrent login over late extension injection

ok 1 - ensureActiveSignerForPubkey prefers concurrent login over late extension injection

duration_ms: 104.059105 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 113.492578

→ Running tests/nostr/adapters.test.mjs TAP version 13

Subtest: createNsecAdapter wires signing and cipher capabilities

ok 1 - createNsecAdapter wires signing and cipher capabilities

duration_ms: 68.343867 type: 'test' ...

Subtest: createNip07Adapter maps extension methods and permissions

ok 2 - createNip07Adapter maps extension methods and permissions

duration_ms: 3.244983 type: 'test' ...

Subtest: createNip46Adapter wraps the remote signer client

ok 3 - createNip46Adapter wraps the remote signer client

duration_ms: 0.682493 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 84.807491

→ Running tests/nostr/cachePolicies.test.mjs TAP version 13

Subtest: CACHE_POLICIES structure

ok 1 - CACHE_POLICIES structure

duration_ms: 1.262135 type: 'test' ...

Subtest: VIDEO_POST policy

ok 2 - VIDEO_POST policy

duration_ms: 0.242378 type: 'test' ...

Subtest: WATCH_HISTORY policy

ok 3 - WATCH_HISTORY policy

duration_ms: 0.166041 type: 'test' ...

Subtest: SUBSCRIPTION_LIST policy

ok 4 - SUBSCRIPTION_LIST policy

duration_ms: 0.161223 type: 'test' ...

Subtest: VIDEO_COMMENT policy

ok 5 - VIDEO_COMMENT policy

duration_ms: 0.355614 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.144914

→ Running tests/nostr/client.test.mjs TAP version 13

Subtest: NostrClient

# Subtest: Initialization
    # Subtest: should initialize with default state
    ok 1 - should initialize with default state
      ---
      duration_ms: 3.575119
      type: 'test'
      ...
    1..1
ok 1 - Initialization
  ---
  duration_ms: 4.508835
  type: 'suite'
  ...
# Subtest: fetchListIncrementally
    # Subtest: should fetch events and deduplicate results
    ok 1 - should fetch events and deduplicate results
      ---
      duration_ms: 3.77674
      type: 'test'
      ...
    # Subtest: should handle incremental updates using lastSeen
    ok 2 - should handle incremental updates using lastSeen
      ---
      duration_ms: 1.752432
      type: 'test'
      ...
    # Subtest: should override lastSeen with explicit since parameter
    ok 3 - should override lastSeen with explicit since parameter
      ---
      duration_ms: 1.288862
      type: 'test'
      ...
    # Subtest: should force full fetch if since is 0
    ok 4 - should force full fetch if since is 0
      ---
      duration_ms: 1.398367
      type: 'test'
      ...
    1..4
ok 2 - fetchListIncrementally
  ---
  duration_ms: 8.6615
  type: 'suite'
  ...
# Subtest: subscribeVideos
    # Subtest: should subscribe to video events and buffer them
    ok 1 - should subscribe to video events and buffer them
      ---
      duration_ms: 301.700089
      type: 'test'
      ...
    1..1
ok 3 - subscribeVideos
  ---
  duration_ms: 302.327207
  type: 'suite'
  ...
# Subtest: fetchVideos
    # Subtest: should delegate to subscribeVideos and return result on eose
    ok 1 - should delegate to subscribeVideos and return result on eose
      ---
      duration_ms: 3.04366
      type: 'test'
      ...
    # Subtest: should respect since parameter if provided
    ok 2 - should respect since parameter if provided
      ---
      duration_ms: 2.159426
      type: 'test'
      ...
    1..2
ok 4 - fetchVideos
  ---
  duration_ms: 5.780961
  type: 'suite'
  ...
# Subtest: publishVideo
    # Subtest: should throw if not logged in
    ok 1 - should throw if not logged in
      ---
      duration_ms: 2.510983
      type: 'test'
      ...
    # Subtest: should sign and publish a valid video
    ok 2 - should sign and publish a valid video
      ---
      duration_ms: 3.822075
      type: 'test'
      ...
    1..2
ok 5 - publishVideo
  ---
  duration_ms: 6.565471
  type: 'suite'
  ...
# Subtest: editVideo
    # Subtest: should throw if not owner
    ok 1 - should throw if not owner
      ---
      duration_ms: 0.90559
      type: 'test'
      ...
    1..1
ok 6 - editVideo
  ---
  duration_ms: 1.042389
  type: 'suite'
  ...
# Subtest: revertVideo
    # Subtest: should publish a deletion marker event
    ok 1 - should publish a deletion marker event
      ---
      duration_ms: 0.555098
      type: 'test'
      ...
    1..1
ok 7 - revertVideo
  ---
  duration_ms: 0.660186
  type: 'suite'
  ...
1..7

ok 1 - NostrClient

duration_ms: 331.162109 type: 'suite' ... 1..1

tests 12

suites 8

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 556.29149

→ Running tests/nostr/comment-events.test.mjs TAP version 13

Subtest: publishComment prefers active signer when available

ok 1 - publishComment prefers active signer when available

duration_ms: 7.319363 type: 'test' ...

Subtest: publishComment rejects when active signer is unavailable

ok 2 - publishComment rejects when active signer is unavailable

duration_ms: 0.384717 type: 'test' ...

Subtest: publishComment accepts legacy targets with only an event id

ok 3 - publishComment accepts legacy targets with only an event id

duration_ms: 1.459577 type: 'test' ...

Subtest: publishComment derives root and parent metadata from parent comment tags

ok 4 - publishComment derives root and parent metadata from parent comment tags

duration_ms: 1.223592 type: 'test' ...

Subtest: listVideoComments matches comments even when tag casing and whitespace differ

ok 5 - listVideoComments matches comments even when tag casing and whitespace differ

duration_ms: 2.409046 type: 'test' ...

Subtest: listVideoComments matches uppercase definition addresses without lowering

ok 6 - listVideoComments matches uppercase definition addresses without lowering

duration_ms: 1.046232 type: 'test' ...

Subtest: publishComment emits only uppercase video event pointer when address and parent are absent

ok 7 - publishComment emits only uppercase video event pointer when address and parent are absent

duration_ms: 1.03781 type: 'test' ...

Subtest: listVideoComments builds filters with uppercase roots plus legacy fallbacks

ok 8 - listVideoComments builds filters with uppercase roots plus legacy fallbacks

duration_ms: 1.366667 type: 'test' ...

Subtest: listVideoComments emits uppercase root filters when only the identifier is known

ok 9 - listVideoComments emits uppercase root filters when only the identifier is known

duration_ms: 1.26279 type: 'test' ...

Subtest: listVideoComments supports legacy targets without a definition address

ok 10 - listVideoComments supports legacy targets without a definition address

duration_ms: 1.086719 type: 'test' ...

Subtest: subscribeVideoComments forwards matching events and cleans up unsubscribe

ok 11 - subscribeVideoComments forwards matching events and cleans up unsubscribe

duration_ms: 9.832623 type: 'test' ...

Subtest: subscribeVideoComments supports video targets without a definition address

ok 12 - subscribeVideoComments supports video targets without a definition address

duration_ms: 0.652357 type: 'test' ... 1..12

tests 12

suites 0

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.897108

→ Running tests/nostr/countDiagnostics.test.mjs TAP version 13

Subtest: countDiagnostics

# Subtest: isVerboseDiagnosticsEnabled
    # Subtest: should return true by default (due to override)
    ok 1 - should return true by default (due to override)
      ---
      duration_ms: 1.168012
      type: 'test'
      ...
    # Subtest: should return false if window.__BITVID_VERBOSE_DEV_MODE__ is false
    ok 2 - should return false if window.__BITVID_VERBOSE_DEV_MODE__ is false
      ---
      duration_ms: 0.488579
      type: 'test'
      ...
    # Subtest: should return true if window.__BITVID_VERBOSE_DEV_MODE__ is true
    ok 3 - should return true if window.__BITVID_VERBOSE_DEV_MODE__ is true
      ---
      duration_ms: 0.403398
      type: 'test'
      ...
    # Subtest: should fall back to isVerboseDevMode if window flag is not boolean
    ok 4 - should fall back to isVerboseDevMode if window flag is not boolean
      ---
      duration_ms: 0.513134
      type: 'test'
      ...
    1..4
ok 1 - isVerboseDiagnosticsEnabled
  ---
  duration_ms: 3.521242
  type: 'suite'
  ...
# Subtest: logRelayCountFailure
    # Subtest: should log warning for new relay URL
    ok 1 - should log warning for new relay URL
      ---
      duration_ms: 1.11886
      type: 'test'
      ...
    # Subtest: should suppress 'Failed to connect to relay' errors
    ok 2 - should suppress 'Failed to connect to relay' errors
      ---
      duration_ms: 0.274437
      type: 'test'
      ...
    # Subtest: should throttle duplicate warnings for the same relay
    ok 3 - should throttle duplicate warnings for the same relay
      ---
      duration_ms: 0.384495
      type: 'test'
      ...
    # Subtest: should handle empty or non-string relay URLs
    ok 4 - should handle empty or non-string relay URLs
      ---
      duration_ms: 0.566046
      type: 'test'
      ...
    1..4
ok 2 - logRelayCountFailure
  ---
  duration_ms: 2.810149
  type: 'suite'
  ...
# Subtest: Other Loggers
    # Subtest: should log timeout cleanup failure once
    ok 1 - should log timeout cleanup failure once
      ---
      duration_ms: 0.481863
      type: 'test'
      ...
    # Subtest: should log rebroadcast failure once
    ok 2 - should log rebroadcast failure once
      ---
      duration_ms: 0.327733
      type: 'test'
      ...
    # Subtest: should log view count failure once
    ok 3 - should log view count failure once
      ---
      duration_ms: 0.354964
      type: 'test'
      ...
    1..3
ok 3 - Other Loggers
  ---
  duration_ms: 1.416107
  type: 'suite'
  ...
# Subtest: Verbose Mode Disabled
    # Subtest: should not log even for new keys when disabled
    ok 1 - should not log even for new keys when disabled
      ---
      duration_ms: 0.325848
      type: 'test'
      ...
    # Subtest: should not consume throttle key when disabled
    ok 2 - should not consume throttle key when disabled
      ---
      duration_ms: 0.342744
      type: 'test'
      ...
    1..2
ok 4 - Verbose Mode Disabled
  ---
  duration_ms: 0.819976
  type: 'suite'
  ...
1..4

ok 1 - countDiagnostics

duration_ms: 13.44901 type: 'suite' ... 1..1

tests 13

suites 5

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 36.63032

→ Running tests/nostr/decryptionSchemeCache.test.mjs TAP version 13

Subtest: decryptionSchemeCache

# Subtest: stores and retrieves a scheme
ok 1 - stores and retrieves a scheme
  ---
  duration_ms: 1.873392
  type: 'test'
  ...
# Subtest: returns null for unknown pubkey
ok 2 - returns null for unknown pubkey
  ---
  duration_ms: 0.368194
  type: 'test'
  ...
# Subtest: handles invalid inputs gracefully
ok 3 - handles invalid inputs gracefully
  ---
  duration_ms: 0.788329
  type: 'test'
  ...
# Subtest: expires entries after TTL (2 hours)
ok 4 - expires entries after TTL (2 hours)
  ---
  duration_ms: 0.656172
  type: 'test'
  ...
# Subtest: does not expire entries before TTL
ok 5 - does not expire entries before TTL
  ---
  duration_ms: 0.278652
  type: 'test'
  ...
# Subtest: clears cache
ok 6 - clears cache
  ---
  duration_ms: 0.255322
  type: 'test'
  ...
1..6

ok 1 - decryptionSchemeCache

duration_ms: 5.944987 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.742247

→ Running tests/nostr/defaultClient.test.mjs TAP version 13

Subtest: Default NostrClient

# Subtest: should be an instance of NostrClient
ok 1 - should be an instance of NostrClient
  ---
  duration_ms: 1.121722
  type: 'test'
  ...
# Subtest: should be configured with default relays
ok 2 - should be configured with default relays
  ---
  duration_ms: 1.296162
  type: 'test'
  ...
# Subtest: should be initialized with default read relays
ok 3 - should be initialized with default read relays
  ---
  duration_ms: 0.209721
  type: 'test'
  ...
# Subtest: should be initialized with default write relays
ok 4 - should be initialized with default write relays
  ---
  duration_ms: 0.280539
  type: 'test'
  ...
# Subtest: should be registered as the default client
ok 5 - should be registered as the default client
  ---
  duration_ms: 0.261514
  type: 'test'
  ...
1..5

ok 1 - Default NostrClient

duration_ms: 4.925751 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.990091

→ Running tests/nostr/dm-direct-message-flow.test.mjs TAP version 13

Subtest: DM relay duplication is deduped

ok 1 - DM relay duplication is deduped

duration_ms: 151.055871 type: 'test' ...

Subtest: out-of-order DM delivery keeps newest messages first

ok 2 - out-of-order DM delivery keeps newest messages first

duration_ms: 88.15987 type: 'test' ...

Subtest: DM reconnect replays do not duplicate messages or reset seen state

ok 3 - DM reconnect replays do not duplicate messages or reset seen state

duration_ms: 177.706281 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 424.729686

→ Running tests/nostr/dmDecryptDiagnostics.test.mjs TAP version 13

Subtest: dmDecryptDiagnostics

# Subtest: summarizeDmEventForLog
    # Subtest: should return a default object when event is null or undefined
    ok 1 - should return a default object when event is null or undefined
      ---
      duration_ms: 2.014438
      type: 'test'
      ...
    # Subtest: should return a default object when event is not an object
    ok 2 - should return a default object when event is not an object
      ---
      duration_ms: 0.22789
      type: 'test'
      ...
    # Subtest: should summarize a valid event correctly
    ok 3 - should summarize a valid event correctly
      ---
      duration_ms: 0.389114
      type: 'test'
      ...
    # Subtest: should handle non-finite created_at
    ok 4 - should handle non-finite created_at
      ---
      duration_ms: 0.326087
      type: 'test'
      ...
    # Subtest: should handle non-finite kind
    ok 5 - should handle non-finite kind
      ---
      duration_ms: 0.245771
      type: 'test'
      ...
    # Subtest: should handle missing or invalid content
    ok 6 - should handle missing or invalid content
      ---
      duration_ms: 0.26281
      type: 'test'
      ...
    # Subtest: should handle missing or invalid tags
    ok 7 - should handle missing or invalid tags
      ---
      duration_ms: 0.322686
      type: 'test'
      ...
    1..7
ok 1 - summarizeDmEventForLog
  ---
  duration_ms: 5.200182
  type: 'suite'
  ...
# Subtest: sanitizeDecryptError
    # Subtest: should return null when error is null or undefined
    ok 1 - should return null when error is null or undefined
      ---
      duration_ms: 0.515575
      type: 'test'
      ...
    # Subtest: should handle string errors
    ok 2 - should handle string errors
      ---
      duration_ms: 0.365066
      type: 'test'
      ...
    # Subtest: should handle Error objects with standard properties
    ok 3 - should handle Error objects with standard properties
      ---
      duration_ms: 0.356169
      type: 'test'
      ...
    # Subtest: should handle Error objects with missing properties
    ok 4 - should handle Error objects with missing properties
      ---
      duration_ms: 0.173116
      type: 'test'
      ...
    # Subtest: should handle Error objects with non-string properties
    ok 5 - should handle Error objects with non-string properties
      ---
      duration_ms: 0.258411
      type: 'test'
      ...
    1..5
ok 2 - sanitizeDecryptError
  ---
  duration_ms: 2.154015
  type: 'suite'
  ...
1..2

ok 1 - dmDecryptDiagnostics

duration_ms: 8.259791 type: 'suite' ... 1..1

tests 12

suites 3

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.630347

→ Running tests/nostr/dmDecryptorPreference.test.mjs TAP version 13

Subtest: decryptDM prefers nip44 decryptors when available

ok 1 - decryptDM prefers nip44 decryptors when available

duration_ms: 3.418369 type: 'test' ...

Subtest: decryptDM falls back to nip04 when nip44 fails

ok 2 - decryptDM falls back to nip04 when nip44 fails

duration_ms: 0.656212 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.584712

→ Running tests/nostr/dmDecryptWorkerClient.test.mjs TAP version 13

Subtest: dmDecryptWorkerClient

# Subtest: should support worker environment
ok 1 - should support worker environment
  ---
  duration_ms: 5.39924
  type: 'test'
  ...
# Subtest: should reject if Worker is unavailable
ok 2 - should reject if Worker is unavailable
  ---
  duration_ms: 3.103819
  type: 'test'
  ...
# Subtest: should reject if inputs are invalid
ok 3 - should reject if inputs are invalid
  ---
  duration_ms: 3.389702
  type: 'test'
  ...
# Subtest: should successfully decrypt message
ok 4 - should successfully decrypt message
  ---
  duration_ms: 9.179674
  type: 'test'
  ...
# Subtest: should handle worker error response
ok 5 - should handle worker error response
  ---
  duration_ms: 3.496063
  type: 'test'
  ...
# Subtest: should handle worker error event
ok 6 - should handle worker error event
  ---
  duration_ms: 4.482559
  type: 'test'
  ...
# Subtest: should timeout if worker does not respond
ok 7 - should timeout if worker does not respond
  ---
  duration_ms: 102.335948
  type: 'test'
  ...
# Subtest: should initialize worker lazily and reuse instance
ok 8 - should initialize worker lazily and reuse instance
  ---
  duration_ms: 3.421463
  type: 'test'
  ...
# Subtest: should recreate worker if creation fails
ok 9 - should recreate worker if creation fails
  ---
  duration_ms: 2.08817
  type: 'test'
  ...
1..9

ok 1 - dmDecryptWorkerClient

duration_ms: 139.933496 type: 'suite' ... 1..1

tests 9

suites 1

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 149.469617

→ Running tests/nostr/dmSignalEvents.test.mjs TAP version 13

Subtest: dmSignalEvents

# Subtest: publishDmReadReceipt should succeed
ok 1 - publishDmReadReceipt should succeed
  ---
  duration_ms: 7.135022
  type: 'test'
  ...
# Subtest: publishDmReadReceipt should fail if session actor
ok 2 - publishDmReadReceipt should fail if session actor
  ---
  duration_ms: 0.933986
  type: 'test'
  ...
# Subtest: publishDmTypingIndicator should succeed
ok 3 - publishDmTypingIndicator should succeed
  ---
  duration_ms: 9.237561
  type: 'test'
  ...
# Subtest: should fail if no signer
ok 4 - should fail if no signer
  ---
  duration_ms: 1.628676
  type: 'test'
  ...
1..4

ok 1 - dmSignalEvents

duration_ms: 149.061919 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 168.773427

→ Running tests/nostr/edit-video-dtag.test.mjs TAP version 13

Subtest: editVideo preserves existing d tag

ok 1 - editVideo preserves existing d tag

duration_ms: 13.325562 type: 'test' ...

Subtest: editVideo falls back to base event id when d tag missing

ok 2 - editVideo falls back to base event id when d tag missing

duration_ms: 2.850257 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.40184

→ Running tests/nostr/eventsCacheStore.test.mjs TAP version 13

Subtest: EventsCacheStore

# Subtest: persistSnapshot should store events and tombstones
ok 1 - persistSnapshot should store events and tombstones
  ---
  duration_ms: 17.397941
  type: 'test'
  ...
# Subtest: persistSnapshot should only update changed items
ok 2 - persistSnapshot should only update changed items
  ---
  duration_ms: 4.151788
  type: 'test'
  ...
# Subtest: persistSnapshot should delete removed items
ok 3 - persistSnapshot should delete removed items
  ---
  duration_ms: 3.366376
  type: 'test'
  ...
# Subtest: persistSnapshot should respect dirty keys optimization
ok 4 - persistSnapshot should respect dirty keys optimization
  ---
  duration_ms: 2.718408
  type: 'test'
  ...
1..4

ok 1 - EventsCacheStore

duration_ms: 29.628467 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 37.830764

→ Running tests/nostr/eventsMap.test.mjs TAP version 13

Subtest: EventsMap functionality

# Subtest: indexes events by author on set
ok 1 - indexes events by author on set
  ---
  duration_ms: 2.672036
  type: 'test'
  ...
# Subtest: updates index on delete
ok 2 - updates index on delete
  ---
  duration_ms: 0.282166
  type: 'test'
  ...
# Subtest: updates index on overwrite with different object
ok 3 - updates index on overwrite with different object
  ---
  duration_ms: 0.375378
  type: 'test'
  ...
# Subtest: clears index on clear
ok 4 - clears index on clear
  ---
  duration_ms: 0.300675
  type: 'test'
  ...
# Subtest: normalizes pubkeys
ok 5 - normalizes pubkeys
  ---
  duration_ms: 0.407646
  type: 'test'
  ...
# Subtest: handles non-event objects gracefully
ok 6 - handles non-event objects gracefully
  ---
  duration_ms: 0.277375
  type: 'test'
  ...
1..6

ok 1 - EventsMap functionality

duration_ms: 6.427545 type: 'test' ... 1..1

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.349195

→ Running tests/nostr/integration-remote-flow.test.mjs TAP version 13

Subtest: nostr login + remote signing + publish + watch history integration

ok 1 - nostr login + remote signing + publish + watch history integration

duration_ms: 158.565459 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 613.938647

→ Running tests/nostr/maxListenerDiagnostics.test.mjs TAP version 13

Subtest: maxListenerDiagnostics

# Subtest: collectCandidateStrings
    # Subtest: should return empty array for null/undefined/falsy
    ok 1 - should return empty array for null/undefined/falsy
      ---
      duration_ms: 1.914573
      type: 'test'
      ...
    # Subtest: should return array with string for string input
    ok 2 - should return array with string for string input
      ---
      duration_ms: 0.567949
      type: 'test'
      ...
    # Subtest: should extract relevant fields from object
    ok 3 - should extract relevant fields from object
      ---
      duration_ms: 0.547929
      type: 'test'
      ...
    # Subtest: should ignore non-string fields in object
    ok 4 - should ignore non-string fields in object
      ---
      duration_ms: 0.605939
      type: 'test'
      ...
    1..4
ok 1 - collectCandidateStrings
  ---
  duration_ms: 4.763154
  type: 'suite'
  ...
# Subtest: shouldSuppressWarning
    # Subtest: should NOT suppress anything if verbose mode is enabled
    ok 1 - should NOT suppress anything if verbose mode is enabled
      ---
      duration_ms: 0.630238
      type: 'test'
      ...
    # Subtest: should suppress warning by code string
    ok 2 - should suppress warning by code string
      ---
      duration_ms: 0.289896
      type: 'test'
      ...
    # Subtest: should suppress warning by object code property
    ok 3 - should suppress warning by object code property
      ---
      duration_ms: 0.295202
      type: 'test'
      ...
    # Subtest: should suppress warning by message snippet
    ok 4 - should suppress warning by message snippet
      ---
      duration_ms: 0.401594
      type: 'test'
      ...
    # Subtest: should suppress warning by object message property snippet
    ok 5 - should suppress warning by object message property snippet
      ---
      duration_ms: 0.407051
      type: 'test'
      ...
    # Subtest: should NOT suppress unrelated warnings
    ok 6 - should NOT suppress unrelated warnings
      ---
      duration_ms: 0.413593
      type: 'test'
      ...
    # Subtest: should handle multiple arguments
    ok 7 - should handle multiple arguments
      ---
      duration_ms: 0.450801
      type: 'test'
      ...
    1..7
ok 2 - shouldSuppressWarning
  ---
  duration_ms: 3.73471
  type: 'suite'
  ...
# Subtest: process.emitWarning patch
    # Subtest: should have patched process.emitWarning
    ok 1 - should have patched process.emitWarning
      ---
      duration_ms: 0.249089
      type: 'test'
      ...
    1..1
ok 3 - process.emitWarning patch
  ---
  duration_ms: 0.40108
  type: 'suite'
  ...
1..3

ok 1 - maxListenerDiagnostics

duration_ms: 10.636091 type: 'suite' ... 1..1

tests 12

suites 4

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 33.692444

→ Running tests/nostr/nip04WorkerClient.test.mjs TAP version 13

Subtest: nip04WorkerClient

# Subtest: should encrypt message successfully via worker
ok 1 - should encrypt message successfully via worker
  ---
  duration_ms: 9.92942
  type: 'test'
  ...
# Subtest: should reject when worker returns error
ok 2 - should reject when worker returns error
  ---
  duration_ms: 5.283488
  type: 'test'
  ...
# Subtest: should reject when worker emits error event
ok 3 - should reject when worker emits error event
  ---
  duration_ms: 3.41695
  type: 'test'
  ...
# Subtest: should timeout if worker does not respond
ok 4 - should timeout if worker does not respond
  ---
  duration_ms: 103.090286
  type: 'test'
  ...
# Subtest: should reject immediately if inputs are missing
ok 5 - should reject immediately if inputs are missing
  ---
  duration_ms: 2.868873
  type: 'test'
  ...
# Subtest: should reject if Worker API is unavailable
ok 6 - should reject if Worker API is unavailable
  ---
  duration_ms: 2.331141
  type: 'test'
  ...
# Subtest: should handle worker creation failure
ok 7 - should handle worker creation failure
  ---
  duration_ms: 1.952128
  type: 'test'
  ...
1..7

ok 1 - nip04WorkerClient

duration_ms: 133.624689 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 145.559987

→ Running tests/nostr/nip07Adapter-race-condition.test.mjs TAP version 13

Subtest: Nip07Adapter Race Condition

# Subtest: should detect capabilities dynamically even if injected late
ok 1 - should detect capabilities dynamically even if injected late
  ---
  duration_ms: 2.612791
  type: 'test'
  ...
# Subtest: should call the injected method even if added late
ok 2 - should call the injected method even if added late
  ---
  duration_ms: 2.126322
  type: 'test'
  ...
1..2

ok 1 - Nip07Adapter Race Condition

duration_ms: 6.325305 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.439307

→ Running tests/nostr/nip07Permissions.test.js TAP version 13

Subtest: writeStoredNip07Permissions normalizes and persists granted methods

ok 1 - writeStoredNip07Permissions normalizes and persists granted methods

duration_ms: 2.310298 type: 'test' ...

Subtest: clearStoredNip07Permissions removes persisted grants

ok 2 - clearStoredNip07Permissions removes persisted grants

duration_ms: 0.359566 type: 'test' ...

Subtest: requestEnablePermissions retries explicit and fallback variants

ok 3 - requestEnablePermissions retries explicit and fallback variants

duration_ms: 1.81248 type: 'test' ...

Subtest: requestEnablePermissions reports unavailable extension

ok 4 - requestEnablePermissions reports unavailable extension

duration_ms: 0.246126 type: 'test' ...

Subtest: waitForNip07Extension resolves when extension is present

ok 5 - waitForNip07Extension resolves when extension is present

duration_ms: 0.543371 type: 'test' ...

Subtest: waitForNip07Extension resolves when extension appears later

ok 6 - waitForNip07Extension resolves when extension appears later

duration_ms: 51.370786 type: 'test' ...

Subtest: waitForNip07Extension rejects when extension never appears

ok 7 - waitForNip07Extension rejects when extension never appears

duration_ms: 101.178344 type: 'test' ... 1..7

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 165.690383

→ Running tests/nostr/nip46Client.test.js TAP version 13

Subtest: Nip46RpcClient encrypts payloads with nip44 conversation keys

ok 1 - Nip46RpcClient encrypts payloads with nip44 conversation keys

duration_ms: 174.0129 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys handles nip44.v2 ciphertext

ok 2 - decryptNip46PayloadWithKeys handles nip44.v2 ciphertext

duration_ms: 24.188594 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys coerces structured handshake payloads

ok 3 - decryptNip46PayloadWithKeys coerces structured handshake payloads

duration_ms: 24.790975 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys decodes buffer-based handshake payloads

ok 4 - decryptNip46PayloadWithKeys decodes buffer-based handshake payloads

duration_ms: 23.249964 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers

ok 5 - decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers

duration_ms: 31.44197 type: 'test' ...

Subtest: parseNip46ConnectionString handles remote signer key hints

ok 6 - parseNip46ConnectionString handles remote signer key hints

duration_ms: 5.240141 type: 'test' ...

Subtest: attemptDecryptNip46HandshakePayload falls back to expected remote signer key

ok 7 - attemptDecryptNip46HandshakePayload falls back to expected remote signer key

duration_ms: 42.495497 type: 'test' ...

Subtest: attemptDecryptNip46HandshakePayload handles array-encoded nip04 payloads

ok 8 - attemptDecryptNip46HandshakePayload handles array-encoded nip04 payloads

duration_ms: 28.851907 type: 'test' ...

Subtest: Nip46RpcClient sendRpc publishes events and resolves responses

ok 9 - Nip46RpcClient sendRpc publishes events and resolves responses

duration_ms: 13.42111 type: 'test' ... 1..9

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 377.297388

→ Running tests/nostr/nip46Connector.test.mjs TAP version 13

Subtest: Nip46Connector

# Subtest: createKeyPair
    # Subtest: should generate a new key pair if none provided
    ok 1 - should generate a new key pair if none provided
      ---
      duration_ms: 3.051931
      type: 'test'
      ...
    # Subtest: should use existing keys if provided
    ok 2 - should use existing keys if provided
      ---
      duration_ms: 0.523107
      type: 'test'
      ...
    1..2
ok 1 - createKeyPair
  ---
  duration_ms: 4.770072
  type: 'suite'
  ...
# Subtest: prepareHandshake
    # Subtest: should parse connection string and return details
    ok 1 - should parse connection string and return details
      ---
      duration_ms: 1.393802
      type: 'test'
      ...
    1..1
ok 2 - prepareHandshake
  ---
  duration_ms: 1.763239
  type: 'suite'
  ...
# Subtest: connect
    # Subtest: should connect directly if remote pubkey is known
    ok 1 - should connect directly if remote pubkey is known
      ---
      duration_ms: 2.292343
      type: 'test'
      ...
    1..1
ok 3 - connect
  ---
  duration_ms: 2.476376
  type: 'suite'
  ...
# Subtest: reconnectStored
    # Subtest: should throw if no stored session
    ok 1 - should throw if no stored session
      ---
      duration_ms: 1.477646
      type: 'test'
      ...
    # Subtest: should reconnect using stored session
    ok 2 - should reconnect using stored session
      ---
      duration_ms: 0.706461
      type: 'test'
      ...
    1..2
ok 4 - reconnectStored
  ---
  duration_ms: 2.498341
  type: 'suite'
  ...
# Subtest: disconnect
    # Subtest: should clear stored session and emit change
    ok 1 - should clear stored session and emit change
      ---
      duration_ms: 0.550517
      type: 'test'
      ...
    1..1
ok 5 - disconnect
  ---
  duration_ms: 0.801318
  type: 'suite'
  ...
1..5

ok 1 - Nip46Connector

duration_ms: 13.490795 type: 'suite' ... 1..1

tests 7

suites 6

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 33.326639

→ Running tests/nostr/nip71.test.js TAP version 13

Subtest: buildNip71MetadataTags normalizes structured fields

ok 1 - buildNip71MetadataTags normalizes structured fields

duration_ms: 3.767914 type: 'test' ...

Subtest: collectNip71PointerRequests aggregates events and tags

ok 2 - collectNip71PointerRequests aggregates events and tags

duration_ms: 0.639782 type: 'test' ...

Subtest: processNip71Events reconciles pointers and filters video hashtags

ok 3 - processNip71Events reconciles pointers and filters video hashtags

duration_ms: 1.377345 type: 'test' ...

Subtest: populateNip71MetadataForVideos fetches missing records once

ok 4 - populateNip71MetadataForVideos fetches missing records once

duration_ms: 1.155945 type: 'test' ...

Subtest: buildNip71VideoEvent composes pointer tags

ok 5 - buildNip71VideoEvent composes pointer tags

duration_ms: 0.934609 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 23.271275

→ Running tests/nostr/nostrClientFacade.test.js TAP version 13

Subtest: nostrClientFacade forwards the default client instance

ok 1 - nostrClientFacade forwards the default client instance

duration_ms: 1.869688 type: 'test' ...

Subtest: nostrClientFacade forwards the default permission helper

ok 2 - nostrClientFacade forwards the default permission helper

duration_ms: 0.214681 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.992122

→ Running tests/nostr/nostrClientRegistry.test.mjs TAP version 13

Subtest: registerSigner stores and resolves signer entries

ok 1 - registerSigner stores and resolves signer entries

duration_ms: 1.476966 type: 'test' ...

Subtest: setActiveSigner notifies listeners and resolves by pubkey

ok 2 - setActiveSigner notifies listeners and resolves by pubkey

duration_ms: 2.182125 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.866674

→ Running tests/nostr/publishHelpers.test.mjs TAP version 13

Subtest: mirrorVideoEvent lowercases provided MIME types

ok 1 - mirrorVideoEvent lowercases provided MIME types

duration_ms: 4.517636 type: 'test' ...

Subtest: mirrorVideoEvent lowercases inferred MIME types

ok 2 - mirrorVideoEvent lowercases inferred MIME types

duration_ms: 0.936961 type: 'test' ...

Subtest: mirrorVideoEvent includes hash tags when provided

ok 3 - mirrorVideoEvent includes hash tags when provided

duration_ms: 1.586358 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.585596

→ Running tests/nostr/reaction-events.test.mjs TAP version 13

Subtest: publishVideoReaction includes both address and event tags when reacting to addressable content

ok 1 - publishVideoReaction includes both address and event tags when reacting to addressable content

duration_ms: 5.763362 type: 'test' ...

Subtest: publishVideoReaction aborts when address pointer is missing a resolvable event id

ok 2 - publishVideoReaction aborts when address pointer is missing a resolvable event id

duration_ms: 0.545375 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.320598

→ Running tests/nostr/revert-video-dtag.test.mjs TAP version 13

Subtest: revertVideo preserves existing d tag

ok 1 - revertVideo preserves existing d tag

duration_ms: 7.463716 type: 'test' ...

Subtest: revertVideo falls back to original event id when d tag missing

ok 2 - revertVideo falls back to original event id when d tag missing

duration_ms: 1.319994 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 22.410576

→ Running tests/nostr/session-actor.test.mjs TAP version 13

Subtest: SessionActor

# Subtest: encrypts and decrypts private key
ok 1 - encrypts and decrypts private key
  ---
  duration_ms: 409.934358
  type: 'test'
  ...
# Subtest: fails to decrypt with wrong passphrase
ok 2 - fails to decrypt with wrong passphrase
  ---
  duration_ms: 416.542219
  type: 'test'
  ...
# Subtest: persists and reads session actor
ok 3 - persists and reads session actor
  ---
  duration_ms: 2.174525
  type: 'test'
  ...
# Subtest: clears stored session actor
ok 4 - clears stored session actor
  ---
  duration_ms: 0.366542
  type: 'test'
  ...
1..4

ok 1 - SessionActor

duration_ms: 832.740383 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 857.042777

→ Running tests/nostr/sessionActor.test.js TAP version 13

Subtest: encryptSessionPrivateKey + decryptSessionPrivateKey roundtrip

ok 1 - encryptSessionPrivateKey + decryptSessionPrivateKey roundtrip

duration_ms: 539.844552 type: 'test' ...

Subtest: persistSessionActor stores encrypted payload metadata

ok 2 - persistSessionActor stores encrypted payload metadata

duration_ms: 252.339477 type: 'test' ...

Subtest: clearStoredSessionActor removes persisted payload

ok 3 - clearStoredSessionActor removes persisted payload

duration_ms: 287.333512 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1093.82099

→ Running tests/nostr/sessionActor.test.mjs TAP version 13

Subtest: js/nostr/sessionActor.js

# Subtest: Encryption and Decryption Roundtrip
ok 1 - Encryption and Decryption Roundtrip
  ---
  duration_ms: 482.660662
  type: 'test'
  ...
# Subtest: Decryption fails with wrong passphrase
ok 2 - Decryption fails with wrong passphrase
  ---
  duration_ms: 521.761641
  type: 'test'
  ...
# Subtest: Persistence and Retrieval
ok 3 - Persistence and Retrieval
  ---
  duration_ms: 525.506893
  type: 'test'
  ...
# Subtest: Clear Stored Session Actor
ok 4 - Clear Stored Session Actor
  ---
  duration_ms: 1.644658
  type: 'test'
  ...
1..4

ok 1 - js/nostr/sessionActor.js

duration_ms: 1537.396557 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1551.376802

→ Running tests/nostr/sign-request-queue.test.mjs TAP version 13

Subtest: queueSignEvent: processes requests sequentially

ok 1 - queueSignEvent: processes requests sequentially

duration_ms: 107.366515 type: 'test' ...

Subtest: queueSignEvent: handles timeouts

ok 2 - queueSignEvent: handles timeouts

duration_ms: 22.591139 type: 'test' ...

Subtest: queueSignEvent: handles permission denied errors

ok 3 - queueSignEvent: handles permission denied errors

duration_ms: 1.173829 type: 'test' ...

Subtest: queueSignEvent: handles signer disconnected

ok 4 - queueSignEvent: handles signer disconnected

duration_ms: 0.759919 type: 'test' ...

Subtest: queueSignEvent: fails if signer is missing or invalid

ok 5 - queueSignEvent: fails if signer is missing or invalid

duration_ms: 0.867814 type: 'test' ...

Subtest: queueSignEvent: continues queue processing after failure

ok 6 - queueSignEvent: continues queue processing after failure

duration_ms: 1.290872 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 220.754417

→ Running tests/nostr/signers.test.mjs TAP version 13

Subtest: setActiveSigner hydrates extension capabilities from window.nostr

ok 1 - setActiveSigner hydrates extension capabilities from window.nostr

duration_ms: 2.663659 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.168044

→ Running tests/nostr/syncMetadataStore.test.mjs TAP version 13

Subtest: SyncMetadataStore

# Subtest: should load from localStorage on initialization
ok 1 - should load from localStorage on initialization
  ---
  duration_ms: 3.413922
  type: 'test'
  ...
# Subtest: should update last seen and save to localStorage
ok 2 - should update last seen and save to localStorage
  ---
  duration_ms: 1.37428
  type: 'test'
  ...
# Subtest: should only update if timestamp is newer
ok 3 - should only update if timestamp is newer
  ---
  duration_ms: 1.38929
  type: 'test'
  ...
# Subtest: should get per-relay last seen
ok 4 - should get per-relay last seen
  ---
  duration_ms: 3.091977
  type: 'test'
  ...
# Subtest: should handle missing localStorage gracefully
ok 5 - should handle missing localStorage gracefully
  ---
  duration_ms: 0.889796
  type: 'test'
  ...
1..5

ok 1 - SyncMetadataStore

duration_ms: 13.713714 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.198465

→ Running tests/nostr/toolkit.test.mjs TAP version 13

Subtest: toolkit: DEFAULT_RELAY_URLS is frozen and contains valid URLs

ok 1 - toolkit: DEFAULT_RELAY_URLS is frozen and contains valid URLs

duration_ms: 1.87849 type: 'test' ...

Subtest: toolkit: resolveSimplePoolConstructor finds SimplePool

ok 2 - toolkit: resolveSimplePoolConstructor finds SimplePool

duration_ms: 0.59075 type: 'test' ...

Subtest: toolkit: shimLegacySimplePoolMethods adds sub/list/map if missing

ok 3 - toolkit: shimLegacySimplePoolMethods adds sub/list/map if missing

duration_ms: 0.962749 type: 'test' ...

Subtest: toolkit: readToolkitFromScope finds NostrTools in global scope

ok 4 - toolkit: readToolkitFromScope finds NostrTools in global scope

duration_ms: 0.387512 type: 'test' ...

Subtest: toolkit: normalizeToolkitCandidate validation

ok 5 - toolkit: normalizeToolkitCandidate validation

duration_ms: 0.805267 type: 'test' ...

Subtest: toolkit: shimLegacySimplePoolMethods handles simple list operation

ok 6 - toolkit: shimLegacySimplePoolMethods handles simple list operation

duration_ms: 14.148584 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.341676

→ Running tests/nostr/videoEventBuffer.test.mjs TAP version 13

Subtest: VideoEventBuffer

# Subtest: Push and Flush
ok 1 - Push and Flush
  ---
  duration_ms: 4.408451
  type: 'test'
  ...
# Subtest: Latest Wins
ok 2 - Latest Wins
  ---
  duration_ms: 0.992749
  type: 'test'
  ...
# Subtest: Tombstone Handling
ok 3 - Tombstone Handling
  ---
  duration_ms: 1.136251
  type: 'test'
  ...
# Subtest: Cleanup
ok 4 - Cleanup
  ---
  duration_ms: 0.849845
  type: 'test'
  ...
1..4

ok 1 - VideoEventBuffer

duration_ms: 9.696652 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.870226

→ Running tests/nostr/videoPayloadBuilder.test.mjs TAP version 13

Subtest: videoPayloadBuilder

# Subtest: extractVideoPublishPayload
    # Subtest: extracts videoData and nip71Metadata
    ok 1 - extracts videoData and nip71Metadata
      ---
      duration_ms: 2.726957
      type: 'test'
      ...
    # Subtest: handles legacyFormData structure
    ok 2 - handles legacyFormData structure
      ---
      duration_ms: 0.296958
      type: 'test'
      ...
    # Subtest: normalizes boolean flags
    ok 3 - normalizes boolean flags
      ---
      duration_ms: 0.371506
      type: 'test'
      ...
    # Subtest: normalizes boolean flags (kids only)
    ok 4 - normalizes boolean flags (kids only)
      ---
      duration_ms: 0.396707
      type: 'test'
      ...
    1..4
ok 1 - extractVideoPublishPayload
  ---
  duration_ms: 5.699925
  type: 'suite'
  ...
# Subtest: prepareVideoPublishPayload
    # Subtest: throws if pubkey is missing
    ok 1 - throws if pubkey is missing
      ---
      duration_ms: 2.015139
      type: 'test'
      ...
    # Subtest: generates a valid event structure
    ok 2 - generates a valid event structure
      ---
      duration_ms: 3.741369
      type: 'test'
      ...
    # Subtest: generates unique d tag and videoRootId if not provided
    ok 3 - generates unique d tag and videoRootId if not provided
      ---
      duration_ms: 0.526627
      type: 'test'
      ...
    # Subtest: uses provided seriesIdentifier as d tag
    ok 4 - uses provided seriesIdentifier as d tag
      ---
      duration_ms: 0.823857
      type: 'test'
      ...
    # Subtest: resolves infoHash from magnet
    ok 5 - resolves infoHash from magnet
      ---
      duration_ms: 0.866422
      type: 'test'
      ...
    # Subtest: includes NIP-71 tags
    ok 6 - includes NIP-71 tags
      ---
      duration_ms: 1.142591
      type: 'test'
      ...
    1..6
ok 2 - prepareVideoPublishPayload
  ---
  duration_ms: 10.409734
  type: 'suite'
  ...
1..2

ok 1 - videoPayloadBuilder

duration_ms: 17.310986 type: 'suite' ... 1..1

tests 10

suites 3

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 43.414694

→ Running tests/nostr/watchHistory.test.js [bitvid] [ProfileCache] Expired watchHistory for 2222222222222222222222222222222222222222222222222222222222222222 TAP version 13

Subtest: buildWatchHistoryPayload enforces byte limits and records skipped entries

ok 1 - buildWatchHistoryPayload enforces byte limits and records skipped entries

duration_ms: 5.096543 type: 'test' ...

Subtest: getWatchHistoryStorage prunes entries that exceed the configured TTL

ok 2 - getWatchHistoryStorage prunes entries that exceed the configured TTL

duration_ms: 4.654526 type: 'test' ...

Subtest: fetchWatchHistory prefers decrypted chunk payloads when nip04 decrypt succeeds

ok 3 - fetchWatchHistory prefers decrypted chunk payloads when nip04 decrypt succeeds

duration_ms: 17.627579 type: 'test' ...

Subtest: fetchWatchHistory falls back to pointer payload when nip04 decrypt fails

ok 4 - fetchWatchHistory falls back to pointer payload when nip04 decrypt fails

duration_ms: 4.276682 type: 'test' ...

Subtest: publishWatchHistorySnapshot uses injected nostr-tools helpers when signer cannot encrypt

ok 5 - publishWatchHistorySnapshot uses injected nostr-tools helpers when signer cannot encrypt

duration_ms: 7.383379 type: 'test' ...

Subtest: publishWatchHistorySnapshot caches successful snapshot results

ok 6 - publishWatchHistorySnapshot caches successful snapshot results

duration_ms: 5.099036 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 57.439594

→ Running tests/nostr/watchHistoryBindings.test.js TAP version 13

Subtest: updateWatchHistoryList delegates to the client's manager

ok 1 - updateWatchHistoryList delegates to the client's manager

duration_ms: 5.508378 type: 'test' ...

Subtest: removeWatchHistoryItem delegates to the client's manager

ok 2 - removeWatchHistoryItem delegates to the client's manager

duration_ms: 1.11203 type: 'test' ...

Subtest: watch history bindings throw when the manager is unavailable

ok 3 - watch history bindings throw when the manager is unavailable

duration_ms: 1.59531 type: 'test' ...

Subtest: watch history bindings throw when a required method is missing

ok 4 - watch history bindings throw when a required method is missing

duration_ms: 0.811027 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 35.472458

→ Running tests/nwc-settings-service.test.mjs TAP version 13

Subtest: nwc settings service preserves settings across profile switches

ok 1 - nwc settings service preserves settings across profile switches

duration_ms: 4.001264 type: 'test' ...

Subtest: updateActiveNwcSettings returns cloned values

ok 2 - updateActiveNwcSettings returns cloned values

duration_ms: 0.526396 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.163985

→ Running tests/performance/resolvePostedAt.test.mjs pool.list calls: 2 pool.get calls: 0 TAP version 13

Subtest: hydrateVideoHistoryBatch optimizes network calls

ok 1 - hydrateVideoHistoryBatch optimizes network calls

duration_ms: 8.268974 type: 'test' ...

Subtest: resolveVideoPostedAtBatch uses batch hydration

ok 2 - resolveVideoPostedAtBatch uses batch hydration

duration_ms: 1.538193 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.78749

→ Running tests/position-floating-panel.test.mjs TAP version 13

Subtest: flips placement when bottom placement would collide with viewport

ok 1 - flips placement when bottom placement would collide with viewport

duration_ms: 637.073555 type: 'test' ...

Subtest: closes previously open popovers without restoring focus

ok 2 - closes previously open popovers without restoring focus

duration_ms: 97.175419 type: 'test' ...

Subtest: restores focus to the trigger when closed

ok 3 - restores focus to the trigger when closed

duration_ms: 53.995652 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 807.299402

→ Running tests/profile-cache.test.mjs TAP version 13

Subtest: ProfileCache: setActiveProfile and getActiveProfile

ok 1 - ProfileCache: setActiveProfile and getActiveProfile

duration_ms: 3.46887 type: 'test' ...

Subtest: ProfileCache: resolveAddressKey

ok 2 - ProfileCache: resolveAddressKey

duration_ms: 0.863969 type: 'test' ...

Subtest: ProfileCache: set and get (memory and persistence)

ok 3 - ProfileCache: set and get (memory and persistence)

duration_ms: 23.969505 type: 'test' ... [bitvid] [ProfileCache] Expired watchHistory for eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

Subtest: ProfileCache: setProfile normalization and storage

ok 4 - ProfileCache: setProfile normalization and storage

duration_ms: 21.884498 type: 'test' ...

Subtest: ProfileCache: TTL expiration

ok 5 - ProfileCache: TTL expiration

duration_ms: 2.296499 type: 'test' ...

Subtest: ProfileCache: clearMemoryCache and clearSignerRuntime

ok 6 - ProfileCache: clearMemoryCache and clearSignerRuntime

duration_ms: 0.61554 type: 'test' ...

Subtest: ProfileCache: listeners

ok 7 - ProfileCache: listeners

duration_ms: 0.519412 type: 'test' ... 1..7

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 77.784796

→ Running tests/profile-modal-controller.test.mjs TAP version 13

Subtest: Profile modal Escape closes and restores trigger focus

ok 1 - Profile modal Escape closes and restores trigger focus

duration_ms: 1500.616887 type: 'test' ...

Subtest: Add profile request suspends focus trap and elevates login modal

ok 2 - Add profile request suspends focus trap and elevates login modal

duration_ms: 1326.171196 type: 'test' ...

Subtest: Profile modal navigation buttons toggle active state

ok 3 - Profile modal navigation buttons toggle active state

duration_ms: 1453.059773 type: 'test' ...

Subtest: Profile modal toggles mobile menu and pane views

ok 4 - Profile modal toggles mobile menu and pane views

duration_ms: 1439.686972 type: 'test' ...

Subtest: wallet URI input masks persisted values and restores on focus

ok 5 - wallet URI input masks persisted values and restores on focus

duration_ms: 1035.150346 type: 'test' ...

Subtest: Profile modal uses abbreviated npub display

ok 6 - Profile modal uses abbreviated npub display

duration_ms: 789.772078 type: 'test' ...

Subtest: renderSavedProfiles applies provider metadata

ok 7 - renderSavedProfiles applies provider metadata

duration_ms: 762.319034 type: 'test' ...

Subtest: Hashtag pane shows empty states by default

ok 8 - Hashtag pane shows empty states by default

duration_ms: 1001.250344 type: 'test' ...

Subtest: Hashtag pane adds, moves, and removes tags

ok 9 - Hashtag pane adds, moves, and removes tags

duration_ms: 1041.784106 type: 'test' ...

Subtest: handleAddHashtagPreference publishes updates

ok 10 - handleAddHashtagPreference publishes updates

duration_ms: 1040.044709 type: 'test' ...

Subtest: Hashtag pane resets after logout when service clears tags

ok 11 - Hashtag pane resets after logout when service clears tags

duration_ms: 1219.892404 type: 'test' ...

Subtest: load() injects markup and caches expected elements

ok 12 - load() injects markup and caches expected elements

duration_ms: 531.146263 type: 'test' ...

Subtest: show()/hide() toggle panes, trap focus, and refresh the wallet pane

ok 13 - show()/hide() toggle panes, trap focus, and refresh the wallet pane

duration_ms: 1029.69887 type: 'test' ...

Subtest: populateProfileRelays renders entries and wires action buttons

ok 14 - populateProfileRelays renders entries and wires action buttons

duration_ms: 540.617865 type: 'test' ...

Subtest: admin mutations invoke accessControl stubs and update admin DOM

ok 15 - admin mutations invoke accessControl stubs and update admin DOM

duration_ms: 526.169799 type: 'test' ...

Subtest: history pane lazily initializes the watch history renderer

ok 16 - history pane lazily initializes the watch history renderer

duration_ms: 1408.55367 type: 'test' ...

Subtest: handleDirectMessagesRelayWarning throttles status updates

ok 17 - handleDirectMessagesRelayWarning throttles status updates

duration_ms: 529.342692 type: 'test' ...

Subtest: handleDirectMessagesRelayWarning suppresses status updates when disabled

ok 18 - handleDirectMessagesRelayWarning suppresses status updates when disabled

duration_ms: 550.713243 type: 'test' ... 1..18

tests 18

suites 0

pass 18

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17736.661003

→ Running tests/reaction-event-builder.test.mjs TAP version 13

Subtest: buildReactionEvent includes pointer and author tags when pubkey provided

ok 1 - buildReactionEvent includes pointer and author tags when pubkey provided

duration_ms: 2.980966 type: 'test' ...

Subtest: buildReactionEvent merges address and event pointers for addressable targets

ok 2 - buildReactionEvent merges address and event pointers for addressable targets

duration_ms: 0.674101 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.997188

→ Running tests/revert-modal-controller.test.mjs TAP version 13

Subtest: RevertModalController

# Subtest: open() fetches history and opens modal
ok 1 - open() fetches history and opens modal
  ---
  duration_ms: 1.60841
  type: 'test'
  ...
# Subtest: open() shows error if not logged in
ok 2 - open() shows error if not logged in
  ---
  duration_ms: 0.449122
  type: 'test'
  ...
# Subtest: handleConfirm() calls revertVideo and refreshes
ok 3 - handleConfirm() calls revertVideo and refreshes
  ---
  duration_ms: 0.473784
  type: 'test'
  ...
1..3

ok 1 - RevertModalController

duration_ms: 4.346042 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.570157

→ Running tests/safe-decode.test.mjs TAP version 13

Subtest: safeDecodeURIComponent returns original value on malformed sequences

ok 1 - safeDecodeURIComponent returns original value on malformed sequences

duration_ms: 1.239909 type: 'test' ...

Subtest: safeDecode helpers handle double-encoded values when applied repeatedly

ok 2 - safeDecode helpers handle double-encoded values when applied repeatedly

duration_ms: 0.355086 type: 'test' ...

Subtest: safeDecodeURIComponentLoose trims inputs by default

ok 3 - safeDecodeURIComponentLoose trims inputs by default

duration_ms: 0.208207 type: 'test' ...

Subtest: safeDecodeURIComponentLoose preserves whitespace when trim is false

ok 4 - safeDecodeURIComponentLoose preserves whitespace when trim is false

duration_ms: 0.226964 type: 'test' ...

Subtest: safeDecodeURIComponent handles empty strings consistently

ok 5 - safeDecodeURIComponent handles empty strings consistently

duration_ms: 0.268766 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.567949

→ Running tests/services/attachmentService.test.mjs TAP version 13

Subtest: attachmentService

# Subtest: prepareAttachmentUpload
    # Subtest: should prepare upload without encryption
    ok 1 - should prepare upload without encryption
      ---
      duration_ms: 3.362794
      type: 'test'
      ...
    # Subtest: should prepare upload with encryption
    ok 2 - should prepare upload with encryption
      ---
      duration_ms: 9.906031
      type: 'test'
      ...
    1..2
ok 1 - prepareAttachmentUpload
  ---
  duration_ms: 14.01471
  type: 'suite'
  ...
# Subtest: downloadAttachment
    # Subtest: should download attachment
    ok 1 - should download attachment
      ---
      duration_ms: 1.840594
      type: 'test'
      ...
    # Subtest: should verify hash
    ok 2 - should verify hash
      ---
      duration_ms: 1.150711
      type: 'test'
      ...
    # Subtest: should throw on hash mismatch
    ok 3 - should throw on hash mismatch
      ---
      duration_ms: 1.32109
      type: 'test'
      ...
    1..3
ok 2 - downloadAttachment
  ---
  duration_ms: 4.789144
  type: 'suite'
  ...
# Subtest: caching
    # Subtest: should cache downloaded attachments
    ok 1 - should cache downloaded attachments
      ---
      duration_ms: 1.073527
      type: 'test'
      ...
    # Subtest: should clear cache
    ok 2 - should clear cache
      ---
      duration_ms: 1.347308
      type: 'test'
      ...
    1..2
ok 3 - caching
  ---
  duration_ms: 2.864331
  type: 'suite'
  ...
1..3

ok 1 - attachmentService

duration_ms: 23.554167 type: 'suite' ... 1..1

tests 7

suites 4

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.457113

→ Running tests/services/authUtils.test.mjs TAP version 13

Subtest: authUtils

# Subtest: normalizeProviderId
    # Subtest: returns trimmed string if valid
    ok 1 - returns trimmed string if valid
      ---
      duration_ms: 1.034059
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if empty
    ok 2 - returns 'nip07' fallback if empty
      ---
      duration_ms: 0.303688
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if null
    ok 3 - returns 'nip07' fallback if null
      ---
      duration_ms: 0.280621
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if undefined
    ok 4 - returns 'nip07' fallback if undefined
      ---
      duration_ms: 0.332145
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if not a string
    ok 5 - returns 'nip07' fallback if not a string
      ---
      duration_ms: 0.192731
      type: 'test'
      ...
    1..5
ok 1 - normalizeProviderId
  ---
  duration_ms: 5.155046
  type: 'test'
  ...
# Subtest: normalizeAuthType
    # Subtest: prioritizes authTypeCandidate
    ok 1 - prioritizes authTypeCandidate
      ---
      duration_ms: 0.521007
      type: 'test'
      ...
    # Subtest: falls back to providerResult.authType
    ok 2 - falls back to providerResult.authType
      ---
      duration_ms: 0.219588
      type: 'test'
      ...
    # Subtest: falls back to providerResult.providerId
    ok 3 - falls back to providerResult.providerId
      ---
      duration_ms: 0.378634
      type: 'test'
      ...
    # Subtest: falls back to providerId
    ok 4 - falls back to providerId
      ---
      duration_ms: 0.222754
      type: 'test'
      ...
    # Subtest: returns 'nip07' if all else fails
    ok 5 - returns 'nip07' if all else fails
      ---
      duration_ms: 0.138216
      type: 'test'
      ...
    # Subtest: trims whitespace from results
    ok 6 - trims whitespace from results
      ---
      duration_ms: 0.222428
      type: 'test'
      ...
    # Subtest: ignores empty strings in candidates
    ok 7 - ignores empty strings in candidates
      ---
      duration_ms: 0.119294
      type: 'test'
      ...
    1..7
ok 2 - normalizeAuthType
  ---
  duration_ms: 3.302014
  type: 'test'
  ...
1..2

ok 1 - authUtils

duration_ms: 9.638616 type: 'test' ... 1..1

tests 15

suites 0

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.810803

→ Running tests/services/discussionCountService.test.mjs TAP version 13

Subtest: DiscussionCountService

# Subtest: initialization sets defaults
ok 1 - initialization sets defaults
  ---
  duration_ms: 1.212347
  type: 'test'
  ...
# Subtest: refreshCounts returns early if videos array is empty or invalid
ok 2 - refreshCounts returns early if videos array is empty or invalid
  ---
  duration_ms: 0.608515
  type: 'test'
  ...
# Subtest: refreshCounts returns early if dependencies are missing
ok 3 - refreshCounts returns early if dependencies are missing
  ---
  duration_ms: 1.087824
  type: 'test'
  ...
# Subtest: refreshCounts handles happy path (fetches and updates DOM)
ok 4 - refreshCounts handles happy path (fetches and updates DOM)
  ---
  duration_ms: 91.764224
  type: 'test'
  ...
# Subtest: refreshCounts uses cached count if available
ok 5 - refreshCounts uses cached count if available
  ---
  duration_ms: 2.571245
  type: 'test'
  ...
# Subtest: refreshCounts handles API errors gracefully
ok 6 - refreshCounts handles API errors gracefully
  ---
  duration_ms: 5.189165
  type: 'test'
  ...
# Subtest: refreshCounts handles partial results
ok 7 - refreshCounts handles partial results
  ---
  duration_ms: 4.680934
  type: 'test'
  ...
# Subtest: refreshCounts handles unsupported relays (empty perRelay)
ok 8 - refreshCounts handles unsupported relays (empty perRelay)
  ---
  duration_ms: 2.560865
  type: 'test'
  ...
1..8

ok 1 - DiscussionCountService

duration_ms: 196.109775 type: 'suite' ... 1..1

tests 8

suites 1

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 216.095412

→ Running tests/services/exploreDataService.optimization.test.mjs TAP version 13

Subtest: exploreDataService optimization

# Subtest: toLightweightVideo extracts necessary fields
ok 1 - toLightweightVideo extracts necessary fields
  ---
  duration_ms: 1.826282
  type: 'test'
  ...
# Subtest: collectVideoTags works with lightweight object
ok 2 - collectVideoTags works with lightweight object
  ---
  duration_ms: 14.254201
  type: 'test'
  ...
# Subtest: buildVideoAddressPointer works with lightweight object
ok 3 - buildVideoAddressPointer works with lightweight object
  ---
  duration_ms: 0.445341
  type: 'test'
  ...
# Subtest: toLightweightVideo handles invalid input
ok 4 - toLightweightVideo handles invalid input
  ---
  duration_ms: 0.229855
  type: 'test'
  ...
1..4

ok 1 - exploreDataService optimization

duration_ms: 18.50437 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.985117

→ Running tests/services/exploreDataService.test.mjs TAP version 13

Subtest: exploreDataService - buildWatchHistoryTagCounts

# Subtest: should return empty Map if watchHistoryService is missing
ok 1 - should return empty Map if watchHistoryService is missing
  ---
  duration_ms: 1.726532
  type: 'test'
  ...
# Subtest: should handle missing loadLatest method gracefully
ok 2 - should handle missing loadLatest method gracefully
  ---
  duration_ms: 0.489723
  type: 'test'
  ...
# Subtest: should handle loadLatest failure gracefully
ok 3 - should handle loadLatest failure gracefully
  ---
  duration_ms: 11.417518
  type: 'test'
  ...
# Subtest: should return counts from worker on success
ok 4 - should return counts from worker on success
  ---
  duration_ms: 3.006298
  type: 'test'
  ...
# Subtest: should handle worker error gracefully
ok 5 - should handle worker error gracefully
  ---
  duration_ms: 2.016664
  type: 'test'
  ...
1..5

ok 1 - exploreDataService - buildWatchHistoryTagCounts

duration_ms: 32.671386 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 43.565515

→ Running tests/services/link-preview-service.test.mjs TAP version 13

Subtest: LinkPreviewService

# Subtest: initializes IndexedDB
ok 1 - initializes IndexedDB
  ---
  duration_ms: 35.280446
  type: 'test'
  ...
# Subtest: fetches and caches preview
ok 2 - fetches and caches preview
  ---
  duration_ms: 74.721747
  type: 'test'
  ...
# Subtest: returns null on fetch failure
ok 3 - returns null on fetch failure
  ---
  duration_ms: 4.880252
  type: 'test'
  ...
# Subtest: respects TTL
ok 4 - respects TTL
  ---
  duration_ms: 20.820233
  type: 'test'
  ...
# Subtest: deletePreview removes from cache
ok 5 - deletePreview removes from cache
  ---
  duration_ms: 6.868775
  type: 'test'
  ...
1..5

ok 1 - LinkPreviewService

duration_ms: 151.644954 type: 'test' ... 1..1

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 169.581732

→ Running tests/services/moderation-action-controller.test.mjs TAP version 13

Subtest: ModerationActionController: handles moderation override

ok 1 - ModerationActionController: handles moderation override

duration_ms: 1.903965 type: 'test' ...

Subtest: ModerationActionController: blocks user and updates state

ok 2 - ModerationActionController: blocks user and updates state

duration_ms: 0.944082 type: 'test' ...

Subtest: ModerationActionController: prevents blocking self

ok 3 - ModerationActionController: prevents blocking self

duration_ms: 0.321023 type: 'test' ...

Subtest: ModerationActionController: requires login to block

ok 4 - ModerationActionController: requires login to block

duration_ms: 0.417539 type: 'test' ...

Subtest: ModerationActionController: handles hide action

ok 5 - ModerationActionController: handles hide action

duration_ms: 0.370571 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.103027

→ Running tests/services/nostr-service.test.mjs TAP version 13

Subtest: NostrService

# Subtest: loadVideos
    # Subtest: should load cached videos and start subscription
    ok 1 - should load cached videos and start subscription
      ---
      duration_ms: 3.462622
      type: 'test'
      ...
    1..1
ok 1 - loadVideos
  ---
  duration_ms: 4.491034
  type: 'suite'
  ...
# Subtest: fetchVideosByAuthors
    # Subtest: should fetch videos from relays for specific authors
    ok 1 - should fetch videos from relays for specific authors
      ---
      duration_ms: 2.353627
      type: 'test'
      ...
    1..1
ok 2 - fetchVideosByAuthors
  ---
  duration_ms: 2.598233
  type: 'suite'
  ...
1..2

ok 1 - NostrService

duration_ms: 8.07044 type: 'suite' ... 1..1

tests 2

suites 3

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.086994

→ Running tests/services/playbackService_forcedSource.test.mjs (node:5060) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time (Use node --trace-warnings ... to show where the warning was created) TAP version 13

Subtest: PlaybackService Forced Source Logic

# Subtest: Normal flow: Timeout triggers fallback to Torrent if URL stalls
ok 1 - Normal flow: Timeout triggers fallback to Torrent if URL stalls
  ---
  duration_ms: 12.225684
  type: 'test'
  ...
# Subtest: Forced Source 'url': Ignores playbackStartTimeout and does NOT fallback to Torrent
ok 2 - Forced Source 'url': Ignores playbackStartTimeout and does NOT fallback to Torrent
  ---
  duration_ms: 4.099116
  type: 'test'
  ...
# Subtest: Forced Source 'url': Does NOT fallback to Torrent even if URL fails (probe bad)
ok 3 - Forced Source 'url': Does NOT fallback to Torrent even if URL fails (probe bad)
  ---
  duration_ms: 2.015267
  type: 'test'
  ...
# Subtest: Forced Source 'torrent': Ignores playbackStartTimeout and does NOT fallback to URL
ok 4 - Forced Source 'torrent': Ignores playbackStartTimeout and does NOT fallback to URL
  ---
  duration_ms: 1.769446
  type: 'test'
  ...
# Subtest: Forced Source 'torrent': Does NOT fallback to URL even if Torrent fails
ok 5 - Forced Source 'torrent': Does NOT fallback to URL even if Torrent fails
  ---
  duration_ms: 1.971403
  type: 'test'
  ...
1..5

ok 1 - PlaybackService Forced Source Logic

duration_ms: 131.505549 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 145.041728

→ Running tests/services/playbackService_order.test.mjs (node:5067) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time (Use node --trace-warnings ... to show where the warning was created) TAP version 13

Subtest: PlaybackService Ordering

# Subtest: Playback Execution Order
    # Subtest: urlFirstEnabled=true: Tries URL first, succeeds
    ok 1 - urlFirstEnabled=true: Tries URL first, succeeds
      ---
      duration_ms: 12.279202
      type: 'test'
      ...
    # Subtest: urlFirstEnabled=true: Tries URL first, fails, falls back to Torrent
    ok 2 - urlFirstEnabled=true: Tries URL first, fails, falls back to Torrent
      ---
      duration_ms: 2.955248
      type: 'test'
      ...
    # Subtest: urlFirstEnabled=false: Tries Torrent first, succeeds
    ok 3 - urlFirstEnabled=false: Tries Torrent first, succeeds
      ---
      duration_ms: 2.284428
      type: 'test'
      ...
    # Subtest: urlFirstEnabled=false: Tries Torrent first, fails (throws), falls back to URL
    ok 4 - urlFirstEnabled=false: Tries Torrent first, fails (throws), falls back to URL
      ---
      duration_ms: 3.460671
      type: 'test'
      ...
    # Subtest: forcedSource=torrent overrides urlFirstEnabled=true
    ok 5 - forcedSource=torrent overrides urlFirstEnabled=true
      ---
      duration_ms: 1.668455
      type: 'test'
      ...
    1..5
ok 1 - Playback Execution Order
  ---
  duration_ms: 24.378743
  type: 'suite'
  ...
1..1

ok 1 - PlaybackService Ordering

duration_ms: 27.048602 type: 'suite' ... 1..1

tests 5

suites 2

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 51.203335

→ Running tests/services/playbackService.test.mjs (node:5074) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time (Use node --trace-warnings ... to show where the warning was created) TAP version 13

Subtest: PlaybackService

# Subtest: Initialization sets defaults and dependencies
ok 1 - Initialization sets defaults and dependencies
  ---
  duration_ms: 1.469963
  type: 'test'
  ...
# Subtest: prepareVideoElement respects localStorage and binds listener
ok 2 - prepareVideoElement respects localStorage and binds listener
  ---
  duration_ms: 4.522254
  type: 'test'
  ...
# Subtest: registerUrlPlaybackWatchdogs triggers onFallback on error
ok 3 - registerUrlPlaybackWatchdogs triggers onFallback on error
  ---
  duration_ms: 1.906049
  type: 'test'
  ...
# Subtest: registerUrlPlaybackWatchdogs triggers onSuccess on playing
ok 4 - registerUrlPlaybackWatchdogs triggers onSuccess on playing
  ---
  duration_ms: 0.767934
  type: 'test'
  ...
# Subtest: createSession returns a PlaybackSession
ok 5 - createSession returns a PlaybackSession
  ---
  duration_ms: 0.499394
  type: 'test'
  ...
# Subtest: PlaybackSession Flow
    # Subtest: URL Probe Success starts playback
    ok 1 - URL Probe Success starts playback
      ---
      duration_ms: 9.198202
      type: 'test'
      ...
    # Subtest: URL Probe Failure triggers fallback
    ok 2 - URL Probe Failure triggers fallback
      ---
      duration_ms: 2.646247
      type: 'test'
      ...
    # Subtest: Watchdog Stall triggers fallback
    ok 3 - Watchdog Stall triggers fallback
      ---
      duration_ms: 3.217408
      type: 'test'
      ...
    1..3
ok 6 - PlaybackSession Flow
  ---
  duration_ms: 15.478137
  type: 'suite'
  ...
1..6

ok 1 - PlaybackService

duration_ms: 109.036125 type: 'suite' ... 1..1

tests 8

suites 2

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 123.764701

→ Running tests/services/profileMetadataService.test.mjs TAP version 13

Subtest: profileMetadataService

# Subtest: fetchProfileMetadataBatch
    # Subtest: should return empty map if no pubkeys provided
    ok 1 - should return empty map if no pubkeys provided
      ---
      duration_ms: 1.945091
      type: 'test'
      ...
    # Subtest: should fetch profiles for provided pubkeys
    ok 2 - should fetch profiles for provided pubkeys
      ---
      duration_ms: 3.478159
      type: 'test'
      ...
    # Subtest: should handle relay failures gracefully
    ok 3 - should handle relay failures gracefully
      ---
      duration_ms: 1.253257
      type: 'test'
      ...
    # Subtest: should parse profile content correctly
    ok 4 - should parse profile content correctly
      ---
      duration_ms: 1.157444
      type: 'test'
      ...
    # Subtest: should deduplicate concurrent requests (inflight)
    ok 5 - should deduplicate concurrent requests (inflight)
      ---
      duration_ms: 8.326434
      type: 'test'
      ...
    1..5
ok 1 - fetchProfileMetadataBatch
  ---
  duration_ms: 17.660711
  type: 'suite'
  ...
# Subtest: fetchProfileMetadata
    # Subtest: should return null for invalid pubkey
    ok 1 - should return null for invalid pubkey
      ---
      duration_ms: 0.498664
      type: 'test'
      ...
    # Subtest: should return single profile result
    ok 2 - should return single profile result
      ---
      duration_ms: 0.564533
      type: 'test'
      ...
    1..2
ok 2 - fetchProfileMetadata
  ---
  duration_ms: 1.778129
  type: 'suite'
  ...
# Subtest: ensureProfileMetadataSubscription
    # Subtest: should return null if nostr pool is missing
    ok 1 - should return null if nostr pool is missing
      ---
      duration_ms: 0.432118
      type: 'test'
      ...
    # Subtest: should create a subscription via relaySubscriptionService
    ok 2 - should create a subscription via relaySubscriptionService
      ---
      duration_ms: 18.537333
      type: 'test'
      ...
    # Subtest: should handle onProfile callback
    ok 3 - should handle onProfile callback
      ---
      duration_ms: 3.09037
      type: 'test'
      ...
    1..3
ok 3 - ensureProfileMetadataSubscription
  ---
  duration_ms: 22.721305
  type: 'suite'
  ...
1..3

ok 1 - profileMetadataService

duration_ms: 43.355511 type: 'suite' ... 1..1

tests 10

suites 4

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 62.523622

→ Running tests/services/r2Service.test.mjs TAP version 13

Subtest: js/services/r2Service.js

# Subtest: Default Settings
ok 1 - Default Settings
  ---
  duration_ms: 2.657645
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Valid Input
ok 2 - handleCloudflareSettingsSubmit - Valid Input
  ---
  duration_ms: 0.617742
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Invalid S3 URL
ok 3 - handleCloudflareSettingsSubmit - Invalid S3 URL
  ---
  duration_ms: 1.021612
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Missing Fields
ok 4 - handleCloudflareSettingsSubmit - Missing Fields
  ---
  duration_ms: 0.654581
  type: 'test'
  ...
# Subtest: ensureBucketConfigForNpub - Uses Explicit Credentials
ok 5 - ensureBucketConfigForNpub - Uses Explicit Credentials
  ---
  duration_ms: 0.75692
  type: 'test'
  ...
# Subtest: ensureBucketConfigForNpub - Uses Meta Bucket when Missing
ok 6 - ensureBucketConfigForNpub - Uses Meta Bucket when Missing
  ---
  duration_ms: 0.433604
  type: 'test'
  ...
# Subtest: resolveConnection - Maps meta bucket into settings
ok 7 - resolveConnection - Maps meta bucket into settings
  ---
  duration_ms: 0.853941
  type: 'test'
  ...
# Subtest: resolveConnection - Returns null without StorageService entries
ok 8 - resolveConnection - Returns null without StorageService entries
  ---
  duration_ms: 0.34575
  type: 'test'
  ...
1..8

ok 1 - js/services/r2Service.js

duration_ms: 13.083306 type: 'test' ... 1..1

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 35.516605

→ Running tests/services/relay-health-service.test.mjs TAP version 13

Subtest: RelayHealthService: initializes with default values

ok 1 - RelayHealthService: initializes with default values

duration_ms: 1.683338 type: 'test' ...

Subtest: RelayHealthService: manages telemetry opt-in

ok 2 - RelayHealthService: manages telemetry opt-in

duration_ms: 0.392362 type: 'test' ...

Subtest: RelayHealthService: getRelayUrls fetches from relayManager

ok 3 - RelayHealthService: getRelayUrls fetches from relayManager

duration_ms: 1.020949 type: 'test' ...

Subtest: RelayHealthService: ensureRelayState creates default state

ok 4 - RelayHealthService: ensureRelayState creates default state

duration_ms: 0.255692 type: 'test' ...

Subtest: RelayHealthService: checkRelay success flow

ok 5 - RelayHealthService: checkRelay success flow

duration_ms: 0.975682 type: 'test' ...

Subtest: RelayHealthService: checkRelay failure flow

ok 6 - RelayHealthService: checkRelay failure flow

duration_ms: 0.540331 type: 'test' ...

Subtest: RelayHealthService: checkRelay handles missing nostrClient

ok 7 - RelayHealthService: checkRelay handles missing nostrClient

duration_ms: 0.351776 type: 'test' ...

Subtest: RelayHealthService: refresh checks all relays

ok 8 - RelayHealthService: refresh checks all relays

duration_ms: 0.729955 type: 'test' ...

Subtest: RelayHealthService: emits telemetry if opted in

ok 9 - RelayHealthService: emits telemetry if opted in

duration_ms: 0.891403 type: 'test' ...

Subtest: RelayHealthService: checkRelay times out after DEFAULT_TIMEOUT_MS

ok 10 - RelayHealthService: checkRelay times out after DEFAULT_TIMEOUT_MS

duration_ms: 13.566519 type: 'test' ...

Subtest: RelayHealthService: failure threshold triggers user warning

ok 11 - RelayHealthService: failure threshold triggers user warning

duration_ms: 0.931311 type: 'test' ...

Subtest: RelayHealthService: user warning respects cooldown

ok 12 - RelayHealthService: user warning respects cooldown

duration_ms: 0.542878 type: 'test' ...

Subtest: RelayHealthService: relay disconnect/error events trigger failure

ok 13 - RelayHealthService: relay disconnect/error events trigger failure

duration_ms: 0.270736 type: 'test' ...

Subtest: RelayHealthService: integrates with nostrClient.markRelayUnreachable

ok 14 - RelayHealthService: integrates with nostrClient.markRelayUnreachable

duration_ms: 0.225733 type: 'test' ... 1..14

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 5018.076569

→ Running tests/services/s3Service.test.mjs TAP version 13

Subtest: s3Service

# Subtest: validateS3Connection
    # Subtest: should validate correct configuration
    ok 1 - should validate correct configuration
      ---
      duration_ms: 1.65883
      type: 'test'
      ...
    # Subtest: should throw on missing required fields
    ok 2 - should throw on missing required fields
      ---
      duration_ms: 1.031357
      type: 'test'
      ...
    1..2
ok 1 - validateS3Connection
  ---
  duration_ms: 3.889259
  type: 'suite'
  ...
# Subtest: getCorsOrigins
    # Subtest: should return current origin
    ok 1 - should return current origin
      ---
      duration_ms: 89.803989
      type: 'test'
      ...
    # Subtest: should handle localhost
    ok 2 - should handle localhost
      ---
      duration_ms: 11.388104
      type: 'test'
      ...
    1..2
ok 2 - getCorsOrigins
  ---
  duration_ms: 101.557047
  type: 'suite'
  ...
1..2

ok 1 - s3Service

duration_ms: 106.254062 type: 'suite' ... 1..1

tests 4

suites 3

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 121.356731

→ Running tests/services/s3UploadService.test.mjs TAP version 13

Subtest: S3UploadService

# Subtest: constructor initializes listeners
ok 1 - constructor initializes listeners
  ---
  duration_ms: 1.381446
  type: 'test'
  ...
# Subtest: Event Emitter Logic
ok 2 - Event Emitter Logic
  ---
  duration_ms: 1.439466
  type: 'test'
  ...
# Subtest: emit handles listener errors safely
ok 3 - emit handles listener errors safely
  ---
  duration_ms: 0.67914
  type: 'test'
  ...
# Subtest: verifyConnection
ok 4 - verifyConnection
  ---
  duration_ms: 0.494785
  type: 'test'
  ...
# Subtest: prepareUpload
ok 5 - prepareUpload
  ---
  duration_ms: 0.491849
  type: 'test'
  ...
# Subtest: uploadFile
    # Subtest: validates parameters
    ok 1 - validates parameters
      ---
      duration_ms: 0.935359
      type: 'test'
      ...
    # Subtest: uploads successfully
    ok 2 - uploads successfully
      ---
      duration_ms: 1.120859
      type: 'test'
      ...
    1..2
ok 6 - uploadFile
  ---
  duration_ms: 2.86831
  type: 'test'
  ...
# Subtest: uploadVideo
    # Subtest: fails if npub missing
    ok 1 - fails if npub missing
      ---
      duration_ms: 1.025931
      type: 'test'
      ...
    # Subtest: fails if title missing
    ok 2 - fails if title missing
      ---
      duration_ms: 0.57371
      type: 'test'
      ...
    # Subtest: fails if file missing
    ok 3 - fails if file missing
      ---
      duration_ms: 0.363521
      type: 'test'
      ...
    # Subtest: successful upload flow
    ok 4 - successful upload flow
      ---
      duration_ms: 2.832303
      type: 'test'
      ...
    # Subtest: uploads thumbnail if provided
    ok 5 - uploads thumbnail if provided
      ---
      duration_ms: 0.654839
      type: 'test'
      ...
    # Subtest: handles validation errors from normalizeVideoNotePayload
    ok 6 - handles validation errors from normalizeVideoNotePayload
      ---
      duration_ms: 0.371578
      type: 'test'
      ...
    # Subtest: handles upload exception
    ok 7 - handles upload exception
      ---
      duration_ms: 0.481769
      type: 'test'
      ...
    1..7
ok 7 - uploadVideo
  ---
  duration_ms: 7.80044
  type: 'test'
  ...
1..7

ok 1 - S3UploadService

duration_ms: 17.785446 type: 'test' ... 1..1

tests 17

suites 0

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.923114

→ Running tests/services/trustBootstrap.test.mjs [bitvid] [bootstrap] Failed to hydrate admin lists for trusted seeds Error: accessControl ready check timed out at AccessControl. (file:///app/tests/services/trustBootstrap.test.mjs:147:13) at Object.apply (node:internal/test_runner/mock/mock:765:20) at waitForAccessControl (file:///app/js/services/trustBootstrap.js:80:39) at hydrate (file:///app/js/services/trustBootstrap.js:138:28) at bootstrapTrustedSeeds (file:///app/js/services/trustBootstrap.js:151:31) at TestContext. (file:///app/tests/services/trustBootstrap.test.mjs:150:11) at Test.runInAsyncScope (node:async_hooks:214:14) at Test.run (node:internal/test_runner/test:1047:25) at async Suite.processPendingSubtests (node:internal/test_runner/test:744:7) [bitvid] [bootstrap] Failed to hydrate admin lists for trusted seeds Error: accessControl ready check timed out at AccessControl. (file:///app/tests/services/trustBootstrap.test.mjs:147:13) at Object.apply (node:internal/test_runner/mock/mock:765:20) at waitForAccessControl (file:///app/js/services/trustBootstrap.js:80:39) at hydrate (file:///app/js/services/trustBootstrap.js:138:28) at runAsyncRetry (file:///app/js/services/trustBootstrap.js:160:13) [bitvid] [bootstrap] Failed to hydrate admin lists for trusted seeds Error: accessControl failed at AccessControl. (file:///app/tests/services/trustBootstrap.test.mjs:164:13) at Object.apply (node:internal/test_runner/mock/mock:765:20) at waitForAccessControl (file:///app/js/services/trustBootstrap.js:80:39) at hydrate (file:///app/js/services/trustBootstrap.js:138:28) at bootstrapTrustedSeeds (file:///app/js/services/trustBootstrap.js:151:31) at TestContext. (file:///app/tests/services/trustBootstrap.test.mjs:171:30) at Test.runInAsyncScope (node:async_hooks:214:14) at Test.run (node:internal/test_runner/test:1047:25) at async Suite.processPendingSubtests (node:internal/test_runner/test:744:7) TAP version 13

Subtest: trustBootstrap

# Subtest: should return early if feature is disabled
ok 1 - should return early if feature is disabled
  ---
  duration_ms: 2.540401
  type: 'test'
  ...
# Subtest: should apply seeds when accessControl is ready
ok 2 - should apply seeds when accessControl is ready
  ---
  duration_ms: 1.494098
  type: 'test'
  ...
# Subtest: should subscribe to accessControl changes
ok 3 - should subscribe to accessControl changes
  ---
  duration_ms: 1.342136
  type: 'test'
  ...
# Subtest: should handle accessControl timeout and apply seeds anyway
ok 4 - should handle accessControl timeout and apply seeds anyway
  ---
  duration_ms: 3.521526
  type: 'test'
  ...
# Subtest: should recompute summaries after applying seeds
ok 5 - should recompute summaries after applying seeds
  ---
  duration_ms: 0.561153
  type: 'test'
  ...
# Subtest: should wait for relays if hydration fails initially
ok 6 - should wait for relays if hydration fails initially
  ---
  duration_ms: 300.643181
  type: 'test'
  ...
1..6

ok 1 - trustBootstrap

duration_ms: 312.119411 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3770.74428

→ Running tests/share-event-builder.test.mjs TAP version 13

Subtest: buildShareEvent preserves share content

ok 1 - buildShareEvent preserves share content

duration_ms: 2.039232 type: 'test' ...

Subtest: buildShareEvent normalizes hex identifiers for e and p tags

ok 2 - buildShareEvent normalizes hex identifiers for e and p tags

duration_ms: 1.881342 type: 'test' ...

Subtest: buildShareEvent includes sanitized relay hints

ok 3 - buildShareEvent includes sanitized relay hints

duration_ms: 0.634989 type: 'test' ...

Subtest: buildShareEvent tolerates missing optional fields

ok 4 - buildShareEvent tolerates missing optional fields

duration_ms: 0.241794 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.902788

→ Running tests/shareNostrController.test.mjs TAP version 13

Subtest: ShareNostrController

# Subtest: handleShare shares video successfully
ok 1 - handleShare shares video successfully
  ---
  duration_ms: 2.137035
  type: 'test'
  ...
# Subtest: handleShare throws error if missing video details
ok 2 - handleShare throws error if missing video details
  ---
  duration_ms: 0.945184
  type: 'test'
  ...
# Subtest: openModal shows error if no video
ok 3 - openModal shows error if no video
  ---
  duration_ms: 0.651259
  type: 'test'
  ...
# Subtest: openModal opens modal with correct payload
ok 4 - openModal opens modal with correct payload
  ---
  duration_ms: 0.444001
  type: 'test'
  ...
1..4

ok 1 - ShareNostrController

duration_ms: 6.41618 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.373207

→ Running tests/sign-request-queue.test.mjs TAP version 13

Subtest: queueSignEvent executes successfully

ok 1 - queueSignEvent executes successfully

duration_ms: 1.740314 type: 'test' ...

Subtest: queueSignEvent executes sequentially for same signer

ok 2 - queueSignEvent executes sequentially for same signer

duration_ms: 102.673369 type: 'test' ...

Subtest: queueSignEvent executes concurrently for different signers

ok 3 - queueSignEvent executes concurrently for different signers

duration_ms: 51.504366 type: 'test' ...

Subtest: queueSignEvent times out

ok 4 - queueSignEvent times out

duration_ms: 52.054321 type: 'test' ...

Subtest: queueSignEvent normalizes errors

ok 5 - queueSignEvent normalizes errors

duration_ms: 0.676425 type: 'test' ...

Subtest: queueSignEvent handles signer disconnect

ok 6 - queueSignEvent handles signer disconnect

duration_ms: 0.756971 type: 'test' ...

Subtest: queueSignEvent handles missing signer

ok 7 - queueSignEvent handles missing signer

duration_ms: 0.381858 type: 'test' ... 1..7

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 365.086746

→ Running tests/state/appState.test.mjs TAP version 13

Subtest: AppState

# Subtest: getters and setters work for simple values
ok 1 - getters and setters work for simple values
  ---
  duration_ms: 1.557779
  type: 'test'
  ...
# Subtest: subscribeToAppStateKey fires on change
ok 2 - subscribeToAppStateKey fires on change
  ---
  duration_ms: 0.822179
  type: 'test'
  ...
# Subtest: subscribeToAppState fires on any change
ok 3 - subscribeToAppState fires on any change
  ---
  duration_ms: 0.39326
  type: 'test'
  ...
# Subtest: setModalState updates state and notifies
ok 4 - setModalState updates state and notifies
  ---
  duration_ms: 0.722145
  type: 'test'
  ...
# Subtest: resetAppState clears everything
ok 5 - resetAppState clears everything
  ---
  duration_ms: 0.687728
  type: 'test'
  ...
# Subtest: setVideosMap only accepts Maps or null
ok 6 - setVideosMap only accepts Maps or null
  ---
  duration_ms: 0.297692
  type: 'test'
  ...
1..6

ok 1 - AppState

duration_ms: 6.155243 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 22.085925

→ Running tests/state/cache-saved-profiles.test.mjs TAP version 13

Subtest: persistSavedProfiles preserves custom authType strings

ok 1 - persistSavedProfiles preserves custom authType strings

duration_ms: 2.082291 type: 'test' ...

Subtest: loadSavedProfilesFromStorage retains custom provider authType

ok 2 - loadSavedProfilesFromStorage retains custom provider authType

duration_ms: 0.603431 type: 'test' ...

Subtest: loadSavedProfilesFromStorage migrates missing authType to nip07

ok 3 - loadSavedProfilesFromStorage migrates missing authType to nip07

duration_ms: 0.341296 type: 'test' ...

Subtest: loadSavedProfilesFromStorage trims stored authType values

ok 4 - loadSavedProfilesFromStorage trims stored authType values

duration_ms: 0.297243 type: 'test' ...

Subtest: loadSavedProfilesFromStorage ignores legacy userPubKey

ok 5 - loadSavedProfilesFromStorage ignores legacy userPubKey

duration_ms: 0.291711 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.144004

→ Running tests/state/cache.test.mjs TAP version 13

Subtest: js/state/cache.js

# Subtest: Saved Profiles Persistence
ok 1 - Saved Profiles Persistence
  ---
  duration_ms: 2.373525
  type: 'test'
  ...
# Subtest: Active Profile Pubkey
ok 2 - Active Profile Pubkey
  ---
  duration_ms: 0.616286
  type: 'test'
  ...
# Subtest: URL Health Caching
ok 3 - URL Health Caching
  ---
  duration_ms: 0.763765
  type: 'test'
  ...
# Subtest: URL Health Expiration
ok 4 - URL Health Expiration
  ---
  duration_ms: 11.37508
  type: 'test'
  ...
# Subtest: Moderation Settings
ok 5 - Moderation Settings
  ---
  duration_ms: 1.334348
  type: 'test'
  ...
# Subtest: Legacy Moderation Overrides Support
    # Subtest: ignores legacy v1 overrides
    ok 1 - ignores legacy v1 overrides
      ---
      duration_ms: 0.440285
      type: 'test'
      ...
    # Subtest: loads v2 overrides
    ok 2 - loads v2 overrides
      ---
      duration_ms: 0.504354
      type: 'test'
      ...
    1..2
ok 6 - Legacy Moderation Overrides Support
  ---
  duration_ms: 1.974586
  type: 'test'
  ...
1..6

ok 1 - js/state/cache.js

duration_ms: 21.67553 type: 'test' ... 1..1

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.867924

→ Running tests/state/profile-cache.test.mjs [bitvid] [ProfileCache] Expired watchHistory for 1111111111111111111111111111111111111111111111111111111111111111 TAP version 13

Subtest: ProfileCache: normalizeHexPubkey validates and cleans inputs

ok 1 - ProfileCache: normalizeHexPubkey validates and cleans inputs

duration_ms: 1.966552 type: 'test' ...

Subtest: ProfileCache: resolves storage keys correctly

ok 2 - ProfileCache: resolves storage keys correctly

duration_ms: 0.581421 type: 'test' ...

Subtest: ProfileCache: persists data to localStorage and memory

ok 3 - ProfileCache: persists data to localStorage and memory

duration_ms: 1.7957 type: 'test' ...

Subtest: ProfileCache: loads from localStorage if memory miss

ok 4 - ProfileCache: loads from localStorage if memory miss

duration_ms: 0.35803 type: 'test' ...

Subtest: ProfileCache: handles active profile switching

ok 5 - ProfileCache: handles active profile switching

duration_ms: 1.004937 type: 'test' ...

Subtest: ProfileCache: setProfile normalizes and saves

ok 6 - ProfileCache: setProfile normalizes and saves

duration_ms: 1.08414 type: 'test' ...

Subtest: ProfileCache: emits events on active profile change

ok 7 - ProfileCache: emits events on active profile change

duration_ms: 0.431529 type: 'test' ...

Subtest: ProfileCache: respects TTL expiration

ok 8 - ProfileCache: respects TTL expiration

duration_ms: 1.35058 type: 'test' ...

Subtest: ProfileCache: setProfile sanitizes XSS in media URLs

ok 9 - ProfileCache: setProfile sanitizes XSS in media URLs

duration_ms: 1.413938 type: 'test' ... 1..9

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.042

→ Running tests/state/profile-settings-store.test.mjs TAP version 13

Subtest: profile settings store clones values and tracks entries

ok 1 - profile settings store clones values and tracks entries

duration_ms: 2.480315 type: 'test' ...

Subtest: profile settings store ignores falsy keys and survives clone failures

ok 2 - profile settings store ignores falsy keys and survives clone failures

duration_ms: 0.344008 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.708361

→ Running tests/storage-service.test.mjs TAP version 13

Subtest: StorageService

# Subtest: init() creates database and object store
ok 1 - init() creates database and object store
  ---
  duration_ms: 11.546101
  type: 'test'
  ...
ok 2 - unlock() generates and stores master key with NIP-44
duration_ms: 10.462536
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Locked storage for 00000000...
[bitvid] [StorageService] Unlocked storage for 00000000...
# Subtest: unlock() restores existing master key
ok 3 - unlock() restores existing master key
---
duration_ms: 4.850841
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
# Subtest: unlock() falls back to NIP-04 if NIP-44 unavailable
ok 4 - unlock() falls back to NIP-04 if NIP-44 unavailable
---
duration_ms: 3.114711
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Locked storage for 00000000...
# Subtest: unlock() normalizes permission denied decrypt errors
ok 5 - unlock() normalizes permission denied decrypt errors
---
duration_ms: 4.204469
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Locked storage for 00000000...
# Subtest: unlock() normalizes missing decryptor errors
ok 6 - unlock() normalizes missing decryptor errors
---
duration_ms: 4.992837
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Locked storage for 00000000...
# Subtest: unlock() normalizes unknown decrypt errors
ok 7 - unlock() normalizes unknown decrypt errors
---
duration_ms: 2.623833
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection conn_1
# Subtest: saveConnection() encrypts and stores connection
ok 8 - saveConnection() encrypts and stores connection
---
duration_ms: 4.644506
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection conn_1
# Subtest: getConnection() decrypts and returns connection
ok 9 - getConnection() decrypts and returns connection
---
duration_ms: 4.789868
type: 'test'
...
# Subtest: getConnection() throws if locked
ok 10 - getConnection() throws if locked
---
duration_ms: 1.320676
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection conn_1
[bitvid] [StorageService] Deleted connection conn_1
# Subtest: deleteConnection() removes connection
ok 11 - deleteConnection() removes connection
---
duration_ms: 3.30144
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection conn_1
[bitvid] [StorageService] Saved connection conn_2
# Subtest: setDefaultConnection() updates metadata
ok 12 - setDefaultConnection() updates metadata
---
duration_ms: 3.674334
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection conn_1
[bitvid] [StorageService] Saved connection conn_2
# Subtest: saveConnection() with defaultForUploads=true clears other defaults
ok 13 - saveConnection() with defaultForUploads=true clears other defaults
---
duration_ms: 3.432341
type: 'test'
...
1..13
ok 1 - StorageService

[bitvid] [StorageService] Unlocked storage for 00000000... # Subtest: unlock() generates and stores master key with NIP-44

duration_ms: 65.481881 type: 'suite' ... 1..1

tests 13

suites 1

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 74.17559

→ Running tests/subscriptions-manager.test.mjs [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay-b.example', backoffMs: 1000, failureCount: 1, retryAt: 1770906701691, reason: null } TAP version 13

Subtest: loadSubscriptions aggregates relay results when one rejects

ok 1 - loadSubscriptions aggregates relay results when one rejects

duration_ms: 11.109054 type: 'test' ...

Subtest: loadSubscriptions queries the correct subscription list kind

ok 2 - loadSubscriptions queries the correct subscription list kind

duration_ms: 1.08623 type: 'test' ...

Subtest: loadSubscriptions falls back to nip44 when hinted

ok 3 - loadSubscriptions falls back to nip44 when hinted

duration_ms: 1.871997 type: 'test' ...

Subtest: loadSubscriptions handles nip44.v2 decryptors

ok 4 - loadSubscriptions handles nip44.v2 decryptors

duration_ms: 1.290028 type: 'test' ...

Subtest: loadSubscriptions prefers nip44 decryptors when both are available

ok 5 - loadSubscriptions prefers nip44 decryptors when both are available

duration_ms: 1.549437 type: 'test' ...

Subtest: loadSubscriptions uses active signer decryptors without requesting extension permissions

ok 6 - loadSubscriptions uses active signer decryptors without requesting extension permissions

duration_ms: 1.259428 type: 'test' ...

Subtest: loadSubscriptions retries permission-required decrypts only when enabled

ok 7 - loadSubscriptions retries permission-required decrypts only when enabled

duration_ms: 1.60768 type: 'test' ...

Subtest: showSubscriptionVideos waits for nostrService warm-up and refreshes after updates

ok 8 - showSubscriptionVideos waits for nostrService warm-up and refreshes after updates

duration_ms: 93.994821 type: 'test' ...

Subtest: ensureLoaded memoizes concurrent loads

ok 9 - ensureLoaded memoizes concurrent loads

duration_ms: 8.609618 type: 'test' ...

Subtest: publishSubscriptionList succeeds with direct signer without requesting extension permissions

ok 10 - publishSubscriptionList succeeds with direct signer without requesting extension permissions

duration_ms: 2.813544 type: 'test' ...

Subtest: publishSubscriptionList prefers nip44 encryption when available

ok 11 - publishSubscriptionList prefers nip44 encryption when available

duration_ms: 1.022212 type: 'test' ...

Subtest: publishSubscriptionList falls back to nip04 when nip44 fails

ok 12 - publishSubscriptionList falls back to nip04 when nip44 fails

duration_ms: 0.963122 type: 'test' ...

Subtest: renderSameGridStyle shows empty state message

ok 13 - renderSameGridStyle shows empty state message

duration_ms: 14.485016 type: 'test' ...

Subtest: renderSameGridStyle forwards moderation badge actions to the application

ok 14 - renderSameGridStyle forwards moderation badge actions to the application

duration_ms: 68.811433 type: 'test' ... 1..14

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 6026.127887

→ Running tests/torrent/service-worker-fallback-message.test.mjs TAP version 13

Subtest: returns generic message when error missing

ok 1 - returns generic message when error missing

duration_ms: 2.657707 type: 'test' ...

Subtest: identifies HTTPS requirement errors

ok 2 - identifies HTTPS requirement errors

duration_ms: 0.249178 type: 'test' ...

Subtest: identifies disabled service worker errors

ok 3 - identifies disabled service worker errors

duration_ms: 0.314304 type: 'test' ...

Subtest: identifies Brave specific guidance

ok 4 - identifies Brave specific guidance

duration_ms: 0.289999 type: 'test' ...

Subtest: identifies blocked script errors

ok 5 - identifies blocked script errors

duration_ms: 0.204548 type: 'test' ...

Subtest: identifies controller claim timeout

ok 6 - identifies controller claim timeout

duration_ms: 0.299904 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.806459

→ Running tests/torrent/style-helpers.test.mjs TAP version 13

Subtest: torrent/ui/styleHelpers

# Subtest: returns null when no element is provided
ok 1 - returns null when no element is provided
  ---
  duration_ms: 1.049147
  type: 'test'
  ...
# Subtest: returns the original element without mutations
ok 2 - returns the original element without mutations
  ---
  duration_ms: 79.455069
  type: 'test'
  ...
# Subtest: provides an empty, frozen fallback map
ok 3 - provides an empty, frozen fallback map
  ---
  duration_ms: 1.526197
  type: 'test'
  ...
# Subtest: no-ops when removing styles
ok 4 - no-ops when removing styles
  ---
  duration_ms: 10.486536
  type: 'test'
  ...
1..4

ok 1 - torrent/ui/styleHelpers

duration_ms: 94.370685 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 108.077501

→ Running tests/torrent/toast-service.test.mjs TAP version 13

Subtest: torrent/ui/toastService

# Subtest: renders a toast with Tailwind token classes and removes it on dismiss
ok 1 - renders a toast with Tailwind token classes and removes it on dismiss
  ---
  duration_ms: 353.762186
  type: 'test'
  ...
1..1

ok 1 - torrent/ui/toastService

duration_ms: 355.418585 type: 'suite' ... 1..1

tests 1

suites 1

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 364.951077

→ Running tests/ui/app-chrome-controller.test.mjs TAP version 13

Subtest: AppChromeController binds upload button when elements hydrate later

ok 1 - AppChromeController binds upload button when elements hydrate later

duration_ms: 93.139901 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 104.774309

→ Running tests/ui/components/debug_hashtag_strip_helper.test.mjs Global ResizeObserver type: function Helper window ResizeObserver: undefined TAP version 13

Subtest: Debug HashtagStripHelper fallback

ok 1 - Debug HashtagStripHelper fallback

duration_ms: 107.701352 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 120.494856

→ Running tests/ui/components/hashtagStripHelper.test.mjs TAP version 13

Subtest: HashtagStripHelper uses ResizeObserver when available

ok 1 - HashtagStripHelper uses ResizeObserver when available

duration_ms: 118.276907 type: 'test' ...

Subtest: HashtagStripHelper falls back to window resize with RAF

ok 2 - HashtagStripHelper falls back to window resize with RAF

duration_ms: 18.745276 type: 'test' ...

Subtest: HashtagStripHelper falls back to window resize with setTimeout when RAF is missing

ok 3 - HashtagStripHelper falls back to window resize with setTimeout when RAF is missing

duration_ms: 14.617613 type: 'test' ...

Subtest: HashtagStripHelper handles teardown correctly

ok 4 - HashtagStripHelper handles teardown correctly

duration_ms: 12.421468 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 184.89381

→ Running tests/ui/components/videoMenuRenderers.test.mjs TAP version 13

Subtest: createVideoMoreMenuPanel - basic rendering

ok 1 - createVideoMoreMenuPanel - basic rendering

duration_ms: 119.75182 type: 'test' ...

Subtest: createVideoMoreMenuPanel - pointer info rendering

ok 2 - createVideoMoreMenuPanel - pointer info rendering

duration_ms: 16.005172 type: 'test' ...

Subtest: createVideoMoreMenuPanel - mirror logic

ok 3 - createVideoMoreMenuPanel - mirror logic

duration_ms: 15.088246 type: 'test' ...

Subtest: createVideoMoreMenuPanel - blacklist logic

ok 4 - createVideoMoreMenuPanel - blacklist logic

duration_ms: 21.363019 type: 'test' ...

Subtest: createVideoShareMenuPanel - permission logic

ok 5 - createVideoShareMenuPanel - permission logic

duration_ms: 16.292098 type: 'test' ...

Subtest: createVideoShareMenuPanel - magnet/cdn logic

ok 6 - createVideoShareMenuPanel - magnet/cdn logic

duration_ms: 11.256125 type: 'test' ...

Subtest: createChannelProfileMenuPanel - basic actions

ok 7 - createChannelProfileMenuPanel - basic actions

duration_ms: 11.791044 type: 'test' ...

Subtest: createVideoSettingsMenuPanel - capabilities

ok 8 - createVideoSettingsMenuPanel - capabilities

duration_ms: 11.764419 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 240.508633

→ Running tests/ui/creatorProfileController.test.mjs TAP version 13

Subtest: CreatorProfileController

# Subtest: resolveCreatorProfileFromSources prioritizes cached profile
ok 1 - resolveCreatorProfileFromSources prioritizes cached profile
  ---
  duration_ms: 3.123473
  type: 'test'
  ...
# Subtest: resolveCreatorProfileFromSources falls back to fetched profile
ok 2 - resolveCreatorProfileFromSources falls back to fetched profile
  ---
  duration_ms: 0.531566
  type: 'test'
  ...
# Subtest: decorateVideoCreatorIdentity modifies video object
ok 3 - decorateVideoCreatorIdentity modifies video object
  ---
  duration_ms: 0.557586
  type: 'test'
  ...
# Subtest: fetchModalCreatorProfile fetches profile and updates modal
ok 4 - fetchModalCreatorProfile fetches profile and updates modal
  ---
  duration_ms: 1.270371
  type: 'test'
  ...
1..4

ok 1 - CreatorProfileController

duration_ms: 7.711661 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.560241

→ Running tests/ui/dm/zapHelpers.test.mjs TAP version 13

Subtest: formatZapAmount

# Subtest: formats standard amounts correctly
ok 1 - formats standard amounts correctly
  ---
  duration_ms: 64.473712
  type: 'test'
  ...
# Subtest: formats compact amounts correctly
ok 2 - formats compact amounts correctly
  ---
  duration_ms: 4.96222
  type: 'test'
  ...
# Subtest: handles zero and small numbers
ok 3 - handles zero and small numbers
  ---
  duration_ms: 1.110916
  type: 'test'
  ...
# Subtest: handles invalid inputs gracefully
ok 4 - handles invalid inputs gracefully
  ---
  duration_ms: 0.972329
  type: 'test'
  ...
# Subtest: handles string number inputs
ok 5 - handles string number inputs
  ---
  duration_ms: 0.642319
  type: 'test'
  ...
1..5

ok 1 - formatZapAmount

duration_ms: 74.483436 type: 'suite' ...

Subtest: aggregateZapTotals

# Subtest: aggregates totals correctly
ok 1 - aggregates totals correctly
  ---
  duration_ms: 0.883157
  type: 'test'
  ...
# Subtest: handles empty input
ok 2 - handles empty input
  ---
  duration_ms: 0.557232
  type: 'test'
  ...
# Subtest: handles non-array input
ok 3 - handles non-array input
  ---
  duration_ms: 0.749339
  type: 'test'
  ...
# Subtest: handles missing or invalid amounts
ok 4 - handles missing or invalid amounts
  ---
  duration_ms: 0.754467
  type: 'test'
  ...
# Subtest: handles missing IDs
ok 5 - handles missing IDs
  ---
  duration_ms: 0.80905
  type: 'test'
  ...
1..5

ok 2 - aggregateZapTotals

duration_ms: 4.904585 type: 'suite' ...

Subtest: normalizeZapReceipt

# Subtest: normalizes valid receipt
ok 1 - normalizes valid receipt
  ---
  duration_ms: 0.426092
  type: 'test'
  ...
# Subtest: normalizes receipt with amount fallback
ok 2 - normalizes receipt with amount fallback
  ---
  duration_ms: 0.244159
  type: 'test'
  ...
# Subtest: normalizes empty receipt
ok 3 - normalizes empty receipt
  ---
  duration_ms: 0.140634
  type: 'test'
  ...
# Subtest: normalizes undefined receipt
ok 4 - normalizes undefined receipt
  ---
  duration_ms: 0.156874
  type: 'test'
  ...
# Subtest: normalizes invalid amount
ok 5 - normalizes invalid amount
  ---
  duration_ms: 0.116081
  type: 'test'
  ...
1..5

ok 3 - normalizeZapReceipt

duration_ms: 1.421011 type: 'suite' ... 1..3

tests 15

suites 3

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 111.714112

→ Running tests/ui/engagement-controller.test.mjs TAP version 13

Subtest: EngagementController

# Subtest: handleRepostAction - should show error if no event ID is available
ok 1 - handleRepostAction - should show error if no event ID is available
  ---
  duration_ms: 3.036047
  type: 'test'
  ...
# Subtest: handleRepostAction - should call repostEvent with correct parameters
ok 2 - handleRepostAction - should call repostEvent with correct parameters
  ---
  duration_ms: 0.758029
  type: 'test'
  ...
# Subtest: handleRepostAction - should handle failure from repostEvent
ok 3 - handleRepostAction - should handle failure from repostEvent
  ---
  duration_ms: 0.662871
  type: 'test'
  ...
# Subtest: handleRepostAction - should use currentVideoPointer when in modal context
ok 4 - handleRepostAction - should use currentVideoPointer when in modal context
  ---
  duration_ms: 0.652319
  type: 'test'
  ...
# Subtest: handleMirrorAction - should show error if video has no URL
ok 5 - handleMirrorAction - should show error if video has no URL
  ---
  duration_ms: 1.433176
  type: 'test'
  ...
# Subtest: handleMirrorAction - should call mirrorVideoEvent when URL is provided
ok 6 - handleMirrorAction - should call mirrorVideoEvent when URL is provided
  ---
  duration_ms: 0.6405
  type: 'test'
  ...
# Subtest: handleMirrorAction - should prevent mirroring private videos
ok 7 - handleMirrorAction - should prevent mirroring private videos
  ---
  duration_ms: 0.379491
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should show error if no event ID
ok 8 - handleEnsurePresenceAction - should show error if no event ID
  ---
  duration_ms: 0.76987
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should handle throttled response
ok 9 - handleEnsurePresenceAction - should handle throttled response
  ---
  duration_ms: 0.864371
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should show success on successful rebroadcast
ok 10 - handleEnsurePresenceAction - should show success on successful rebroadcast
  ---
  duration_ms: 0.531484
  type: 'test'
  ...
1..10

ok 1 - EngagementController

duration_ms: 13.705479 type: 'test' ... 1..1

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 33.276106

→ Running tests/ui/hashtag-strip-helper-bounce.test.mjs TAP version 13

Subtest: HashtagStripHelper triggers scroll hint when content overflows

ok 1 - HashtagStripHelper triggers scroll hint when content overflows

duration_ms: 114.603262 type: 'test' ...

Subtest: HashtagStripHelper does not trigger scroll hint when no overflow

ok 2 - HashtagStripHelper does not trigger scroll hint when no overflow

duration_ms: 15.48275 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 148.982561

→ Running tests/ui/popoverEngine.test.mjs TAP version 13

Subtest: opens a popover in the overlay root and positions the panel

ok 1 - opens a popover in the overlay root and positions the panel

duration_ms: 601.447854 type: 'test' ...

Subtest: positions bottom-end panels flush with the trigger's right edge

ok 2 - positions bottom-end panels flush with the trigger's right edge

duration_ms: 89.12484 type: 'test' ...

Subtest: closes on outside pointer events and restores focus

ok 3 - closes on outside pointer events and restores focus

duration_ms: 49.846079 type: 'test' ...

Subtest: supports roving focus, home/end navigation, and typeahead

ok 4 - supports roving focus, home/end navigation, and typeahead

duration_ms: 64.461464 type: 'test' ...

Subtest: escape closes the popover and restores trigger focus

ok 5 - escape closes the popover and restores trigger focus

duration_ms: 53.28361 type: 'test' ...

Subtest: close respects restoreFocus option for contextual menus

ok 6 - close respects restoreFocus option for contextual menus

duration_ms: 189.956602 type: 'test' ...

Subtest: ensures only one popover is open at a time

ok 7 - ensures only one popover is open at a time

duration_ms: 247.004756 type: 'test' ...

Subtest: applies token-based sizing and arrow positioning

ok 8 - applies token-based sizing and arrow positioning

duration_ms: 61.751322 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1367.765244

→ Running tests/ui/profile-modal-moderation-settings.test.mjs TAP version 13

Subtest: moderation settings save updates service and disables control

ok 1 - moderation settings save updates service and disables control

duration_ms: 203.602178 type: 'test' ...

Subtest: moderation reset restores defaults and clearing inputs uses defaults

ok 2 - moderation reset restores defaults and clearing inputs uses defaults

duration_ms: 38.419132 type: 'test' ...

Subtest: guest fallback uses config moderation defaults

ok 3 - guest fallback uses config moderation defaults

duration_ms: 31.003594 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 290.282227

→ Running tests/ui/profileModalController-addProfile.test.mjs Success: Profile added. Select it when you're ready to switch. Success: That profile is already saved on this device. TAP version 13

Subtest: handleAddProfile adds a new profile correctly

ok 1 - handleAddProfile adds a new profile correctly

duration_ms: 201.386743 type: 'test' ...

Subtest: handleAddProfile prevents duplicates

ok 2 - handleAddProfile prevents duplicates

duration_ms: 35.947527 type: 'test' ...

Subtest: handleAddProfile handles missing login result

ok 3 - handleAddProfile handles missing login result

duration_ms: 30.708539 type: 'test' ...

Subtest: handleAddProfile handles errors gracefully

ok 4 - handleAddProfile handles errors gracefully

duration_ms: 33.882274 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 318.570721

→ Running tests/ui/reactionController.test.mjs TAP version 13

Subtest: ReactionController

# Subtest: should subscribe to reactions
ok 1 - should subscribe to reactions
  ---
  duration_ms: 3.581947
  type: 'test'
  ...
# Subtest: should handle reaction update from subscription
ok 2 - should handle reaction update from subscription
  ---
  duration_ms: 0.830409
  type: 'test'
  ...
# Subtest: should apply optimistic update on handleReaction
ok 3 - should apply optimistic update on handleReaction
  ---
  duration_ms: 1.430876
  type: 'test'
  ...
# Subtest: should rollback optimistic update on publish failure
ok 4 - should rollback optimistic update on publish failure
  ---
  duration_ms: 1.118191
  type: 'test'
  ...
# Subtest: should not react if user not logged in
ok 5 - should not react if user not logged in
  ---
  duration_ms: 1.328988
  type: 'test'
  ...
1..5

ok 1 - ReactionController

duration_ms: 10.867654 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.869231

→ Running tests/ui/similar-content-card.test.mjs TAP version 13

Subtest: cached thumbnails reuse existing src without lazy-loading

ok 1 - cached thumbnails reuse existing src without lazy-loading

duration_ms: 374.168713 type: 'test' ...

Subtest: uncached thumbnails use fallback, cache on load, and retain blur state

ok 2 - uncached thumbnails use fallback, cache on load, and retain blur state

duration_ms: 41.107898 type: 'test' ...

Subtest: primary clicks trigger onPlay while modifiers and right clicks do not

ok 3 - primary clicks trigger onPlay while modifiers and right clicks do not

duration_ms: 27.309751 type: 'test' ...

Subtest: author identity fields render supplied values and datasets

ok 4 - author identity fields render supplied values and datasets

duration_ms: 21.781994 type: 'test' ...

Subtest: view counter wiring exposes pointer datasets

ok 5 - view counter wiring exposes pointer datasets

duration_ms: 22.911012 type: 'test' ... 1..5

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 507.285564

→ Running tests/ui/similarContentController.test.mjs TAP version 13

Subtest: SimilarContentController

# Subtest: extractDTagValue
    # Subtest: should return the 'd' tag value
    ok 1 - should return the 'd' tag value
      ---
      duration_ms: 1.824223
      type: 'test'
      ...
    # Subtest: should return empty string if no 'd' tag
    ok 2 - should return empty string if no 'd' tag
      ---
      duration_ms: 0.477778
      type: 'test'
      ...
    1..2
ok 1 - extractDTagValue
  ---
  duration_ms: 3.285651
  type: 'suite'
  ...
# Subtest: computeCandidates
    # Subtest: should return empty array if no active video
    ok 1 - should return empty array if no active video
      ---
      duration_ms: 1.987103
      type: 'test'
      ...
    # Subtest: should compute similar candidates based on tags
    ok 2 - should compute similar candidates based on tags
      ---
      duration_ms: 1.533478
      type: 'test'
      ...
    # Subtest: should exclude blocked authors
    ok 3 - should exclude blocked authors
      ---
      duration_ms: 0.999653
      type: 'test'
      ...
    # Subtest: should prioritize higher shared tag count
    ok 4 - should prioritize higher shared tag count
      ---
      duration_ms: 0.79291
      type: 'test'
      ...
    1..4
ok 2 - computeCandidates
  ---
  duration_ms: 5.806959
  type: 'suite'
  ...
# Subtest: updateModal
    # Subtest: should clear similar content if no active video
    ok 1 - should clear similar content if no active video
      ---
      duration_ms: 0.437905
      type: 'test'
      ...
    # Subtest: should set similar content if matches found
    ok 2 - should set similar content if matches found
      ---
      duration_ms: 0.85403
      type: 'test'
      ...
    # Subtest: should clear similar content if no matches found
    ok 3 - should clear similar content if no matches found
      ---
      duration_ms: 0.769425
      type: 'test'
      ...
    1..3
ok 3 - updateModal
  ---
  duration_ms: 2.531719
  type: 'suite'
  ...
1..3

ok 1 - SimilarContentController

duration_ms: 12.759171 type: 'suite' ... 1..1

tests 9

suites 4

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.015722

→ Running tests/ui/storageService.test.mjs TAP version 13 [bitvid] [StorageService] Unlocked storage for 00000000...

Subtest: StorageService

# Subtest: should unlock storage with a valid signer
ok 1 - should unlock storage with a valid signer
  ---
  duration_ms: 26.528238
  type: 'test'
  ...
ok 2 - should save and retrieve an R2 connection
duration_ms: 11.308539
type: 'test'
...
[bitvid] [StorageService] Unlocked storage for 00000000...
[bitvid] [StorageService] Saved connection s3-generic-1
# Subtest: should save and retrieve a Generic S3 connection with extra fields
ok 3 - should save and retrieve a Generic S3 connection with extra fields
---
duration_ms: 5.993534
type: 'test'
...
# Subtest: should fail to get connection if locked
ok 4 - should fail to get connection if locked
---
duration_ms: 1.729308
type: 'test'
...
1..4
ok 1 - StorageService

[bitvid] [StorageService] Unlocked storage for 00000000... [bitvid] [StorageService] Saved connection r2-test-1 # Subtest: should save and retrieve an R2 connection

duration_ms: 190.718306 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 200.8166

→ Running tests/ui/tag-pill-list.test.mjs TAP version 13

Subtest: renderTagPillStrip builds buttons with labels and icons

ok 1 - renderTagPillStrip builds buttons with labels and icons

duration_ms: 104.374776 type: 'test' ...

Subtest: renderTagPillStrip applies preference state styling

ok 2 - renderTagPillStrip applies preference state styling

duration_ms: 14.956507 type: 'test' ...

Subtest: renderTagPillStrip wires the activation callback

ok 3 - renderTagPillStrip wires the activation callback

duration_ms: 13.947808 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 148.108947

→ Running tests/ui/tag-preference-menu.test.mjs TAP version 13

Subtest: createTagPreferenceMenu renders heading and actions

ok 1 - createTagPreferenceMenu renders heading and actions

duration_ms: 101.957562 type: 'test' ...

Subtest: createTagPreferenceMenu disables actions based on membership and login

ok 2 - createTagPreferenceMenu disables actions based on membership and login

duration_ms: 18.836097 type: 'test' ...

Subtest: createTagPreferenceMenu forwards actions to callback

ok 3 - createTagPreferenceMenu forwards actions to callback

duration_ms: 17.27832 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 153.229546

→ Running tests/ui/tagPreferenceMenuController.test.mjs TAP version 13

Subtest: TagPreferenceMenuController

# Subtest: ensurePopover
    # Subtest: should create a new popover entry if one does not exist
    ok 1 - should create a new popover entry if one does not exist
      ---
      duration_ms: 3.146936
      type: 'test'
      ...
    # Subtest: should return existing entry if one exists
    ok 2 - should return existing entry if one exists
      ---
      duration_ms: 0.757927
      type: 'test'
      ...
    # Subtest: should return null if trigger or tag is missing
    ok 3 - should return null if trigger or tag is missing
      ---
      duration_ms: 1.613216
      type: 'test'
      ...
    1..3
ok 1 - ensurePopover
  ---
  duration_ms: 7.707898
  type: 'suite'
  ...
# Subtest: requestMenu
    # Subtest: should ensure popover, close others, and open the requested one
    ok 1 - should ensure popover, close others, and open the requested one
      ---
      duration_ms: 2.271289
      type: 'test'
      ...
    # Subtest: should close the popover if it is already open
    ok 2 - should close the popover if it is already open
      ---
      duration_ms: 0.828928
      type: 'test'
      ...
    1..2
ok 2 - requestMenu
  ---
  duration_ms: 3.598337
  type: 'suite'
  ...
# Subtest: handleMenuAction
    # Subtest: should call service method and notify update
    ok 1 - should call service method and notify update
      ---
      duration_ms: 1.002806
      type: 'test'
      ...
    # Subtest: should handle error and call showError
    ok 2 - should handle error and call showError
      ---
      duration_ms: 1.610765
      type: 'test'
      ...
    1..2
ok 3 - handleMenuAction
  ---
  duration_ms: 3.229053
  type: 'suite'
  ...
# Subtest: persistPreferencesFromMenu
    # Subtest: should call service.publish
    ok 1 - should call service.publish
      ---
      duration_ms: 1.081546
      type: 'test'
      ...
    # Subtest: should reuse in-flight promise
    ok 2 - should reuse in-flight promise
      ---
      duration_ms: 0.841621
      type: 'test'
      ...
    1..2
ok 4 - persistPreferencesFromMenu
  ---
  duration_ms: 2.304956
  type: 'suite'
  ...
1..4

ok 1 - TagPreferenceMenuController

duration_ms: 18.698564 type: 'suite' ... 1..1

tests 9

suites 5

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 48.476053

→ Running tests/ui/torrentStatusController.test.mjs TAP version 13

Subtest: TorrentStatusController throws if accessor is missing

ok 1 - TorrentStatusController throws if accessor is missing

duration_ms: 2.165513 type: 'test' ...

Subtest: TorrentStatusController updates video modal and calls onRemovePoster

ok 2 - TorrentStatusController updates video modal and calls onRemovePoster

duration_ms: 0.967149 type: 'test' ...

Subtest: TorrentStatusController handles missing modal gracefully

ok 3 - TorrentStatusController handles missing modal gracefully

duration_ms: 0.414692 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 24.284827

→ Running tests/ui/uploadModal-integration.test.mjs TAP version 13

Subtest: UploadModal Integration

# Subtest: should detect default R2 connection and show summary when loaded and unlocked
ok 1 - should detect default R2 connection and show summary when loaded and unlocked
  ---
  duration_ms: 61.107754
  type: 'test'
  ...
# Subtest: should handle locked state and unlock flow
ok 2 - should handle locked state and unlock flow
  ---
  duration_ms: 36.16853
  type: 'test'
  ...
1..2

ok 1 - UploadModal Integration

duration_ms: 12370.197962 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12381.565964

→ Running tests/ui/uploadModal-reset.test.mjs TAP version 13

Subtest: UploadModal Reset Logic

# Subtest: should reset upload state and inputs when resetUploads is called
ok 1 - should reset upload state and inputs when resetUploads is called
  ---
  duration_ms: 83.152118
  type: 'test'
  ...
# Subtest: should guard against zombie callbacks when upload is reset during process
ok 2 - should guard against zombie callbacks when upload is reset during process
  ---
  duration_ms: 41.672484
  type: 'test'
  ...
# Subtest: should call resetUploads when close is called
ok 3 - should call resetUploads when close is called
  ---
  duration_ms: 24.303516
  type: 'test'
  ...
1..3

ok 1 - UploadModal Reset Logic

duration_ms: 12422.28384 type: 'suite' ... 1..1

tests 3

suites 1

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12437.167684

→ Running tests/ui/url-health-controller.test.mjs TAP version 13

Subtest: UrlHealthController

# Subtest: probeUrl returns ok for valid URL
ok 1 - probeUrl returns ok for valid URL
  ---
  duration_ms: 150.299423
  type: 'test'
  ...
# Subtest: probeUrl returns error for 404
ok 2 - probeUrl returns error for 404
  ---
  duration_ms: 27.114937
  type: 'test'
  ...
# Subtest: handleUrlHealthBadge updates badge
ok 3 - handleUrlHealthBadge updates badge
  ---
  duration_ms: 167.975432
  type: 'test'
  ...
# Subtest: getUrlHealthPlaceholderMarkup returns string
ok 4 - getUrlHealthPlaceholderMarkup returns string
  ---
  duration_ms: 10.808902
  type: 'test'
  ...
1..4

ok 1 - UrlHealthController

duration_ms: 358.755468 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 371.921268

→ Running tests/ui/video-list-view-popular-tags.test.mjs TAP version 13

Subtest: VideoListView renders sorted popular tag pills

duration_ms: 156.151119 type: 'test' ...

Subtest: VideoListView applies tag preference variants to popular tags

duration_ms: 27.045759 type: 'test' ...

Subtest: VideoListView hides popular tags when no tags are available

duration_ms: 16.797573 type: 'test' ...

Subtest: VideoListView setModerationBlockHandler stores callable

ok 4 - VideoListView setModerationBlockHandler stores callable

duration_ms: 9.381039 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 223.019123

→ Running tests/ui/video-list-view-sorting.test.mjs TAP version 13

Subtest: VideoListView sorts cards by original posted timestamp

ok 1 - VideoListView sorts cards by original posted timestamp

duration_ms: 138.790977 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 155.161041

→ Running tests/unit/app_guard_logic.test.mjs TAP version 13

Subtest: Application.resetTorrentStats logic

# Subtest: does not throw when videoModal is null
ok 1 - does not throw when videoModal is null
  ---
  duration_ms: 0.930404
  type: 'test'
  ...
# Subtest: does not throw when videoModal is undefined
ok 2 - does not throw when videoModal is undefined
  ---
  duration_ms: 0.216976
  type: 'test'
  ...
# Subtest: does not throw when videoModal lacks resetStats
ok 3 - does not throw when videoModal lacks resetStats
  ---
  duration_ms: 0.27021
  type: 'test'
  ...
# Subtest: calls resetStats when available
ok 4 - calls resetStats when available
  ---
  duration_ms: 0.320758
  type: 'test'
  ...
1..4

ok 1 - Application.resetTorrentStats logic

duration_ms: 5.060913 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.53493

→ Running tests/unit/client-count-resilience.test.mjs TAP version 13

Subtest: NostrClient resilience to COUNT timeouts

ok 1 - NostrClient resilience to COUNT timeouts

duration_ms: 3408.870883 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3416.632474

→ Running tests/unit/comment-avatar.test.mjs TAP version 13

Subtest: normalizeCommentAvatarKey handles inputs correctly

ok 1 - normalizeCommentAvatarKey handles inputs correctly

duration_ms: 1.479206 type: 'test' ...

Subtest: resolveCommentAvatarAsset resolves avatars correctly

ok 2 - resolveCommentAvatarAsset resolves avatars correctly

duration_ms: 3.251655 type: 'test' ...

Subtest: registerCommentAvatarFailure registers failures correctly

ok 3 - registerCommentAvatarFailure registers failures correctly

duration_ms: 0.589599 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.25331

→ Running tests/unit/editModalController.test.mjs TAP version 13

Subtest: EditModalController

# Subtest: open()
    # Subtest: should resolve target and open modal if authorized
    ok 1 - should resolve target and open modal if authorized
      ---
      duration_ms: 2.368792
      type: 'test'
      ...
    # Subtest: should show error if not logged in
    ok 2 - should show error if not logged in
      ---
      duration_ms: 0.933867
      type: 'test'
      ...
    # Subtest: should show error if user does not own video
    ok 3 - should show error if user does not own video
      ---
      duration_ms: 0.583831
      type: 'test'
      ...
    # Subtest: should handle modal load errors
    ok 4 - should handle modal load errors
      ---
      duration_ms: 0.934426
      type: 'test'
      ...
    1..4
ok 1 - open()
  ---
  duration_ms: 5.927287
  type: 'suite'
  ...
# Subtest: handleSubmit()
    # Subtest: should handle successful submission
    ok 1 - should handle successful submission
      ---
      duration_ms: 1.047095
      type: 'test'
      ...
    # Subtest: should show error if not logged in
    ok 2 - should show error if not logged in
      ---
      duration_ms: 0.587543
      type: 'test'
      ...
    # Subtest: should handle submission errors
    ok 3 - should handle submission errors
      ---
      duration_ms: 1.522197
      type: 'test'
      ...
    1..3
ok 2 - handleSubmit()
  ---
  duration_ms: 3.85583
  type: 'suite'
  ...
1..2

ok 1 - EditModalController

duration_ms: 10.667945 type: 'suite' ... 1..1

tests 7

suites 3

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.494605

→ Running tests/unit/embed_player_modal.test.mjs TAP version 13

Subtest: EmbedPlayerModal interface

# Subtest: has expected methods
ok 1 - has expected methods
  ---
  duration_ms: 2.95981
  type: 'test'
  ...
# Subtest: resetStats is no-op and does not throw
ok 2 - resetStats is no-op and does not throw
  ---
  duration_ms: 0.437683
  type: 'test'
  ...
1..2

ok 1 - EmbedPlayerModal interface

duration_ms: 5.313972 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.060005

→ Running tests/unit/embed-accent.test.mjs TAP version 13

Subtest: embed accent color configuration

ok 1 - embed accent color configuration

duration_ms: 1.762611 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.709864

→ Running tests/unit/hashChangeHandler.test.mjs TAP version 13

Subtest: createHashChangeHandler

# Subtest: loads default view if hash is empty
ok 1 - loads default view if hash is empty
  ---
  duration_ms: 1.17776
  type: 'test'
  ...
# Subtest: redirects legacy view
ok 2 - redirects legacy view
  ---
  duration_ms: 0.263396
  type: 'test'
  ...
# Subtest: skips redundant reload
ok 3 - skips redundant reload
  ---
  duration_ms: 0.343289
  type: 'test'
  ...
1..3

ok 1 - createHashChangeHandler

duration_ms: 3.588934 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.587802

→ Running tests/unit/notificationController.test.mjs TAP version 13

Subtest: NotificationController

# Subtest: should instantiate correctly
ok 1 - should instantiate correctly
  ---
  duration_ms: 2.542451
  type: 'test'
  ...
# Subtest: showError should update text content and show container
ok 2 - showError should update text content and show container
  ---
  duration_ms: 1.011945
  type: 'test'
  ...
# Subtest: showError should hide container when msg is empty
ok 3 - showError should hide container when msg is empty
  ---
  duration_ms: 1.199473
  type: 'test'
  ...
# Subtest: updateNotificationPortalVisibility should toggle active class on portal
ok 4 - updateNotificationPortalVisibility should toggle active class on portal
  ---
  duration_ms: 0.90115
  type: 'test'
  ...
# Subtest: showSuccess should update text content and show container
ok 5 - showSuccess should update text content and show container
  ---
  duration_ms: 0.721851
  type: 'test'
  ...
# Subtest: showStatus should update text content and show container
ok 6 - showStatus should update text content and show container
  ---
  duration_ms: 0.830914
  type: 'test'
  ...
# Subtest: showStatus should handle spinner option
ok 7 - showStatus should handle spinner option
  ---
  duration_ms: 0.74581
  type: 'test'
  ...
1..7

ok 1 - NotificationController

duration_ms: 9.754476 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 27.153291

→ Running tests/unit/searchFilters.test.mjs TAP version 13

Subtest: parseFilterQuery collects filters and text tokens

ok 1 - parseFilterQuery collects filters and text tokens

duration_ms: 4.702813 type: 'test' ...

Subtest: serializeFiltersToQuery emits filter tokens in order

ok 2 - serializeFiltersToQuery emits filter tokens in order

duration_ms: 0.547077 type: 'test' ...

Subtest: video search filter matcher enforces tags, duration, and nsfw when allowed

ok 3 - video search filter matcher enforces tags, duration, and nsfw when allowed

duration_ms: 0.759625 type: 'test' ...

Subtest: video search filter matcher blocks nsfw when disallowed and requires url

ok 4 - video search filter matcher blocks nsfw when disallowed and requires url

duration_ms: 0.364788 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.700921

→ Running tests/unit/security-config.test.mjs TAP version 13

Subtest: Security configuration: Development mode should be disabled in production

ok 1 - Security configuration: Development mode should be disabled in production

duration_ms: 2.947533 type: 'test' ...

Subtest: Security configuration: Verbose diagnostics should be disabled in production

ok 2 - Security configuration: Verbose diagnostics should be disabled in production

duration_ms: 0.243819 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.148989

→ Running tests/unit/services/moderationDecorator.test.mjs TAP version 13

Subtest: ModerationDecorator

# Subtest: deriveModerationReportType
    # Subtest: should return empty string for null summary
    ok 1 - should return empty string for null summary
      ---
      duration_ms: 2.649294
      type: 'test'
      ...
    # Subtest: should return empty string for empty types
    ok 2 - should return empty string for empty types
      ---
      duration_ms: 0.261682
      type: 'test'
      ...
    # Subtest: should return the type with highest trusted count
    ok 3 - should return the type with highest trusted count
      ---
      duration_ms: 0.38577
      type: 'test'
      ...
    1..3
ok 1 - deriveModerationReportType
  ---
  duration_ms: 5.522071
  type: 'suite'
  ...
# Subtest: deriveModerationTrustedCount
    # Subtest: should return trusted count for specific type
    ok 1 - should return trusted count for specific type
      ---
      duration_ms: 0.968826
      type: 'test'
      ...
    # Subtest: should fall back to totalTrusted if type not found
    ok 2 - should fall back to totalTrusted if type not found
      ---
      duration_ms: 0.338469
      type: 'test'
      ...
    1..2
ok 2 - deriveModerationTrustedCount
  ---
  duration_ms: 1.803571
  type: 'suite'
  ...
# Subtest: getReporterDisplayName
    # Subtest: should return name from cache if available
    ok 1 - should return name from cache if available
      ---
      duration_ms: 0.591911
      type: 'test'
      ...
    # Subtest: should return short formatted string if not in cache
    ok 2 - should return short formatted string if not in cache
      ---
      duration_ms: 1.401139
      type: 'test'
      ...
    1..2
ok 3 - getReporterDisplayName
  ---
  duration_ms: 2.783754
  type: 'suite'
  ...
# Subtest: decorateVideo
    # Subtest: should return the video object if input is invalid
    ok 1 - should return the video object if input is invalid
      ---
      duration_ms: 0.783426
      type: 'test'
      ...
    # Subtest: should decorate video with basic moderation
    ok 2 - should decorate video with basic moderation
      ---
      duration_ms: 0.674775
      type: 'test'
      ...
    # Subtest: should flag video as hidden if trusted mute count exceeds threshold
    ok 3 - should flag video as hidden if trusted mute count exceeds threshold
      ---
      duration_ms: 0.741715
      type: 'test'
      ...
    1..3
ok 4 - decorateVideo
  ---
  duration_ms: 2.713984
  type: 'suite'
  ...
# Subtest: updateSettings
    # Subtest: should update moderation settings and affect decoration
    ok 1 - should update moderation settings and affect decoration
      ---
      duration_ms: 0.793642
      type: 'test'
      ...
    1..1
ok 5 - updateSettings
  ---
  duration_ms: 1.143038
  type: 'suite'
  ...
1..5

ok 1 - ModerationDecorator

duration_ms: 16.986325 type: 'suite' ... 1..1

tests 11

suites 6

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 69.457961

→ Running tests/unit/services/r2Service.storage-config.test.mjs TAP version 13 [bitvid] [R2] Verifying access for Bucket: 'r2-meta-bucket' in Account: 'acc…23'

Subtest: R2Service bucket selection

# Subtest: uses meta.bucket for ensureBucketExists and multipartUpload
ok 1 - uses meta.bucket for ensureBucketExists and multipartUpload
  ---
  duration_ms: 43.735746
  type: 'test'
  ...
# Subtest: uses configured meta.bucket when verifying public access
ok 2 - uses configured meta.bucket when verifying public access
  ---
  duration_ms: 2.982042
  type: 'test'
  ...
1..2

ok 1 - R2Service bucket selection

duration_ms: 49.085707 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 64.627253

→ Running tests/utils/cardSourceVisibility.test.mjs TAP version 13

Subtest: cardSourceVisibility

# Subtest: updateVideoCardSourceVisibility
    # Subtest: should handle null or undefined input gracefully
    ok 1 - should handle null or undefined input gracefully
      ---
      duration_ms: 10.15611
      type: 'test'
      ...
    # Subtest: should resolve card from object with .card property
    ok 2 - should resolve card from object with .card property
      ---
      duration_ms: 4.146076
      type: 'test'
      ...
    # Subtest: should always show card if owner is viewer
    ok 3 - should always show card if owner is viewer
      ---
      duration_ms: 4.451908
      type: 'test'
      ...
    # Subtest: should show card if at least one source is healthy
    ok 4 - should show card if at least one source is healthy
      ---
      duration_ms: 4.4794
      type: 'test'
      ...
    # Subtest: should show card if at least one source is checking/pending
    ok 5 - should show card if at least one source is checking/pending
      ---
      duration_ms: 3.063519
      type: 'test'
      ...
    # Subtest: should hide card ONLY if both sources are failed (not healthy and not pending)
    ok 6 - should hide card ONLY if both sources are failed (not healthy and not pending)
      ---
      duration_ms: 2.131681
      type: 'test'
      ...
    # Subtest: should recover from hidden state when source becomes healthy
    ok 7 - should recover from hidden state when source becomes healthy
      ---
      duration_ms: 1.610826
      type: 'test'
      ...
    # Subtest: should check closest .card if element is not .card itself
    ok 8 - should check closest .card if element is not .card itself
      ---
      duration_ms: 13.385383
      type: 'test'
      ...
    1..8
ok 1 - updateVideoCardSourceVisibility
  ---
  duration_ms: 45.973406
  type: 'suite'
  ...
1..1

ok 1 - cardSourceVisibility

duration_ms: 47.480136 type: 'suite' ... 1..1

tests 8

suites 2

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 86.519459

→ Running tests/utils/domUtils.test.mjs TAP version 13

Subtest: domUtils

# Subtest: escapeHTML
    # Subtest: should return empty string for null or undefined
    ok 1 - should return empty string for null or undefined
      ---
      duration_ms: 3.173139
      type: 'test'
      ...
    # Subtest: should return the string as is if no special characters are present
    ok 2 - should return the string as is if no special characters are present
      ---
      duration_ms: 0.591172
      type: 'test'
      ...
    # Subtest: should escape special characters
    ok 3 - should escape special characters
      ---
      duration_ms: 0.72455
      type: 'test'
      ...
    # Subtest: should escape multiple occurrences of special characters
    ok 4 - should escape multiple occurrences of special characters
      ---
      duration_ms: 0.650133
      type: 'test'
      ...
    # Subtest: should handle mixed content correctly
    ok 5 - should handle mixed content correctly
      ---
      duration_ms: 0.542577
      type: 'test'
      ...
    # Subtest: should convert non-string inputs to string and escape
    ok 6 - should convert non-string inputs to string and escape
      ---
      duration_ms: 0.451401
      type: 'test'
      ...
    1..6
ok 1 - escapeHTML
  ---
  duration_ms: 8.884011
  type: 'suite'
  ...
# Subtest: removeTrackingScripts
    # Subtest: should do nothing if root is null or undefined
    ok 1 - should do nothing if root is null or undefined
      ---
      duration_ms: 1.051212
      type: 'test'
      ...
    # Subtest: should do nothing if root has no querySelectorAll
    ok 2 - should do nothing if root has no querySelectorAll
      ---
      duration_ms: 1.027993
      type: 'test'
      ...
    # Subtest: should remove scripts matching the tracking pattern
    ok 3 - should remove scripts matching the tracking pattern
      ---
      duration_ms: 45.912373
      type: 'test'
      ...
    # Subtest: should not remove inline scripts (no src)
    ok 4 - should not remove inline scripts (no src)
      ---
      duration_ms: 6.053008
      type: 'test'
      ...
    # Subtest: should remove scripts where src ends with tracking.js
    ok 5 - should remove scripts where src ends with tracking.js
      ---
      duration_ms: 3.01539
      type: 'test'
      ...
    # Subtest: should remove scripts where src contains /tracking.js
    ok 6 - should remove scripts where src contains /tracking.js
      ---
      duration_ms: 2.273536
      type: 'test'
      ...
    1..6
ok 2 - removeTrackingScripts
  ---
  duration_ms: 60.976738
  type: 'suite'
  ...
1..2

ok 1 - domUtils

duration_ms: 72.148862 type: 'suite' ... 1..1

tests 12

suites 3

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 123.743214

→ Running tests/utils/hex.test.mjs TAP version 13

Subtest: hex utils

# Subtest: normalizeHexString
    # Subtest: should return empty string for non-string inputs
    ok 1 - should return empty string for non-string inputs
      ---
      duration_ms: 1.212781
      type: 'test'
      ...
    # Subtest: should return empty string for empty or whitespace-only strings
    ok 2 - should return empty string for empty or whitespace-only strings
      ---
      duration_ms: 0.387299
      type: 'test'
      ...
    # Subtest: should trim and lowercase valid hex strings
    ok 3 - should trim and lowercase valid hex strings
      ---
      duration_ms: 0.329546
      type: 'test'
      ...
    1..3
ok 1 - normalizeHexString
  ---
  duration_ms: 3.244131
  type: 'suite'
  ...
# Subtest: aliases
    # Subtest: should export normalizeHexId as an alias
    ok 1 - should export normalizeHexId as an alias
      ---
      duration_ms: 0.348393
      type: 'test'
      ...
    # Subtest: should export normalizeHexPubkey as an alias
    ok 2 - should export normalizeHexPubkey as an alias
      ---
      duration_ms: 0.211949
      type: 'test'
      ...
    1..2
ok 2 - aliases
  ---
  duration_ms: 1.004396
  type: 'suite'
  ...
# Subtest: HEX64_REGEX
    # Subtest: should match valid 64-character hex strings
    ok 1 - should match valid 64-character hex strings
      ---
      duration_ms: 0.859454
      type: 'test'
      ...
    # Subtest: should not match strings with incorrect length
    ok 2 - should not match strings with incorrect length
      ---
      duration_ms: 0.811547
      type: 'test'
      ...
    # Subtest: should not match strings with non-hex characters
    ok 3 - should not match strings with non-hex characters
      ---
      duration_ms: 0.452324
      type: 'test'
      ...
    # Subtest: should not match empty strings
    ok 4 - should not match empty strings
      ---
      duration_ms: 0.458017
      type: 'test'
      ...
    1..4
ok 3 - HEX64_REGEX
  ---
  duration_ms: 3.932372
  type: 'suite'
  ...
# Subtest: normalizeHexHash
    # Subtest: should return empty string for non-string inputs
    ok 1 - should return empty string for non-string inputs
      ---
      duration_ms: 0.60087
      type: 'test'
      ...
    # Subtest: should return empty string for invalid hex
    ok 2 - should return empty string for invalid hex
      ---
      duration_ms: 0.258261
      type: 'test'
      ...
    # Subtest: should return normalized hex for valid inputs
    ok 3 - should return normalized hex for valid inputs
      ---
      duration_ms: 0.221216
      type: 'test'
      ...
    1..3
ok 4 - normalizeHexHash
  ---
  duration_ms: 1.587204
  type: 'suite'
  ...
1..4

ok 1 - hex utils

duration_ms: 11.457269 type: 'suite' ... 1..1

tests 12

suites 5

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 149.499234

→ Running tests/utils/linkPreviewSettings.test.mjs TAP version 13

Subtest: linkPreviewSettings

# Subtest: getLinkPreviewSettings
    # Subtest: returns defaults when storage is empty
    ok 1 - returns defaults when storage is empty
      ---
      duration_ms: 8.07037
      type: 'test'
      ...
    # Subtest: returns stored settings
    ok 2 - returns stored settings
      ---
      duration_ms: 6.124711
      type: 'test'
      ...
    # Subtest: returns defaults when storage is invalid
    ok 3 - returns defaults when storage is invalid
      ---
      duration_ms: 3.145477
      type: 'test'
      ...
    # Subtest: sanitizes settings from storage
    ok 4 - sanitizes settings from storage
      ---
      duration_ms: 1.913814
      type: 'test'
      ...
    1..4
ok 1 - getLinkPreviewSettings
  ---
  duration_ms: 22.472646
  type: 'suite'
  ...
# Subtest: setLinkPreviewAutoFetch
    # Subtest: updates settings and persists to storage
    ok 1 - updates settings and persists to storage
      ---
      duration_ms: 3.07489
      type: 'test'
      ...
    # Subtest: emits an event on change
    ok 2 - emits an event on change
      ---
      duration_ms: 3.592867
      type: 'test'
      ...
    1..2
ok 2 - setLinkPreviewAutoFetch
  ---
  duration_ms: 7.766656
  type: 'suite'
  ...
# Subtest: allowLinkPreviewDomain
    # Subtest: adds a domain and persists to storage
    ok 1 - adds a domain and persists to storage
      ---
      duration_ms: 2.355605
      type: 'test'
      ...
    # Subtest: handles duplicates
    ok 2 - handles duplicates
      ---
      duration_ms: 1.519101
      type: 'test'
      ...
    # Subtest: normalizes domains
    ok 3 - normalizes domains
      ---
      duration_ms: 1.804789
      type: 'test'
      ...
    # Subtest: emits an event on change
    ok 4 - emits an event on change
      ---
      duration_ms: 1.559698
      type: 'test'
      ...
    # Subtest: respects silent option
    ok 5 - respects silent option
      ---
      duration_ms: 1.171279
      type: 'test'
      ...
    1..5
ok 3 - allowLinkPreviewDomain
  ---
  duration_ms: 9.891335
  type: 'suite'
  ...
# Subtest: isLinkPreviewDomainAllowed
    # Subtest: returns true for allowed domains
    ok 1 - returns true for allowed domains
      ---
      duration_ms: 1.599085
      type: 'test'
      ...
    # Subtest: returns false for disallowed domains
    ok 2 - returns false for disallowed domains
      ---
      duration_ms: 0.806145
      type: 'test'
      ...
    # Subtest: handles normalization
    ok 3 - handles normalization
      ---
      duration_ms: 1.757453
      type: 'test'
      ...
    # Subtest: accepts settings object as second argument
    ok 4 - accepts settings object as second argument
      ---
      duration_ms: 0.871044
      type: 'test'
      ...
    1..4
ok 4 - isLinkPreviewDomainAllowed
  ---
  duration_ms: 5.615777
  type: 'suite'
  ...
# Subtest: subscribeToLinkPreviewSettings
    # Subtest: calls callback on event emission
    ok 1 - calls callback on event emission
      ---
      duration_ms: 1.860538
      type: 'test'
      ...
    # Subtest: unsubscribes correctly
    ok 2 - unsubscribes correctly
      ---
      duration_ms: 1.653955
      type: 'test'
      ...
    1..2
ok 5 - subscribeToLinkPreviewSettings
  ---
  duration_ms: 4.058824
  type: 'suite'
  ...
1..5

ok 1 - linkPreviewSettings

duration_ms: 53.033594 type: 'suite' ... 1..1

tests 17

suites 6

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 145.499879

→ Running tests/utils/lruCache.test.mjs TAP version 13

Subtest: LRUCache

# Subtest: should initialize with default options
ok 1 - should initialize with default options
  ---
  duration_ms: 2.46741
  type: 'test'
  ...
# Subtest: should store and retrieve values
ok 2 - should store and retrieve values
  ---
  duration_ms: 0.742415
  type: 'test'
  ...
# Subtest: should evict oldest item when limit is reached
ok 3 - should evict oldest item when limit is reached
  ---
  duration_ms: 0.57686
  type: 'test'
  ...
# Subtest: should refresh recency on access
ok 4 - should refresh recency on access
  ---
  duration_ms: 0.729564
  type: 'test'
  ...
# Subtest: should update value and refresh recency on set
ok 5 - should update value and refresh recency on set
  ---
  duration_ms: 1.69153
  type: 'test'
  ...
# Subtest: should track stats
ok 6 - should track stats
  ---
  duration_ms: 0.915031
  type: 'test'
  ...
# Subtest: should clear cache
ok 7 - should clear cache
  ---
  duration_ms: 1.044199
  type: 'test'
  ...
1..7

ok 1 - LRUCache

duration_ms: 11.892023 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 53.108251

→ Running tests/utils/profileMedia.test.mjs TAP version 13

Subtest: sanitizeProfileMediaUrl handles non-string inputs

ok 1 - sanitizeProfileMediaUrl handles non-string inputs

duration_ms: 6.394144 type: 'test' ...

Subtest: sanitizeProfileMediaUrl handles empty or whitespace-only strings

ok 2 - sanitizeProfileMediaUrl handles empty or whitespace-only strings

duration_ms: 0.922853 type: 'test' ...

Subtest: sanitizeProfileMediaUrl trims whitespace and removes quotes

ok 3 - sanitizeProfileMediaUrl trims whitespace and removes quotes

duration_ms: 1.997207 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows data:image/ URLs

ok 4 - sanitizeProfileMediaUrl allows data:image/ URLs

duration_ms: 0.259834 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows blob: URLs

ok 5 - sanitizeProfileMediaUrl allows blob: URLs

duration_ms: 0.435485 type: 'test' ...

Subtest: sanitizeProfileMediaUrl rejects specific placeholder images

ok 6 - sanitizeProfileMediaUrl rejects specific placeholder images

duration_ms: 0.875293 type: 'test' ...

Subtest: sanitizeProfileMediaUrl normalizes IPFS URLs

ok 7 - sanitizeProfileMediaUrl normalizes IPFS URLs

duration_ms: 0.92166 type: 'test' ...

Subtest: sanitizeProfileMediaUrl handles protocol-relative URLs

ok 8 - sanitizeProfileMediaUrl handles protocol-relative URLs

duration_ms: 0.472034 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows relative paths

ok 9 - sanitizeProfileMediaUrl allows relative paths

duration_ms: 1.593339 type: 'test' ...

Subtest: sanitizeProfileMediaUrl adds protocol to domains and localhost

ok 10 - sanitizeProfileMediaUrl adds protocol to domains and localhost

duration_ms: 6.155292 type: 'test' ...

Subtest: sanitizeProfileMediaUrl coerces http to https except for localhost

ok 11 - sanitizeProfileMediaUrl coerces http to https except for localhost

duration_ms: 0.83314 type: 'test' ...

Subtest: sanitizeProfileMediaUrl rejects unsupported patterns

ok 12 - sanitizeProfileMediaUrl rejects unsupported patterns

duration_ms: 1.642051 type: 'test' ... 1..12

tests 12

suites 0

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 83.833972

→ Running tests/utils/video-deduper.test.mjs TAP version 13

Subtest: dedupeToNewestByRoot replaces entries with missing timestamps

ok 1 - dedupeToNewestByRoot replaces entries with missing timestamps

duration_ms: 1.93055 type: 'test' ...

Subtest: dedupeToNewestByRoot replaces entries with non-numeric timestamps

ok 2 - dedupeToNewestByRoot replaces entries with non-numeric timestamps

duration_ms: 0.50934 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 35.45038

→ Running tests/utils/video-tags.test.mjs TAP version 13

Subtest: collectVideoTags dedupes across metadata sources and respects casing

ok 1 - collectVideoTags dedupes across metadata sources and respects casing

duration_ms: 50.065595 type: 'test' ...

Subtest: collectVideoTags sorts case-insensitively and adds hashes when requested

ok 2 - collectVideoTags sorts case-insensitively and adds hashes when requested

duration_ms: 1.037089 type: 'test' ...

Subtest: collectVideoTags handles malformed inputs safely

ok 3 - collectVideoTags handles malformed inputs safely

duration_ms: 0.917817 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 89.318014

→ Running tests/utils/videoPointer.test.mjs TAP version 13

Subtest: resolveVideoPointer returns address pointer with dTag

ok 1 - resolveVideoPointer returns address pointer with dTag

duration_ms: 5.311131 type: 'test' ...

Subtest: resolveVideoPointer returns address pointer with videoRootId

ok 2 - resolveVideoPointer returns address pointer with videoRootId

duration_ms: 1.062901 type: 'test' ...

Subtest: resolveVideoPointer returns event pointer with fallbackEventId

ok 3 - resolveVideoPointer returns event pointer with fallbackEventId

duration_ms: 0.529458 type: 'test' ...

Subtest: resolveVideoPointer prioritizes dTag over videoRootId

ok 4 - resolveVideoPointer prioritizes dTag over videoRootId

duration_ms: 0.392056 type: 'test' ...

Subtest: resolveVideoPointer prioritizes videoRootId over fallbackEventId

ok 5 - resolveVideoPointer prioritizes videoRootId over fallbackEventId

duration_ms: 0.484548 type: 'test' ...

Subtest: resolveVideoPointer prioritizes dTag over fallbackEventId

ok 6 - resolveVideoPointer prioritizes dTag over fallbackEventId

duration_ms: 0.524102 type: 'test' ...

Subtest: resolveVideoPointer includes relay in pointer

ok 7 - resolveVideoPointer includes relay in pointer

duration_ms: 1.02341 type: 'test' ...

Subtest: resolveVideoPointer normalizes inputs

ok 8 - resolveVideoPointer normalizes inputs

duration_ms: 0.684408 type: 'test' ...

Subtest: resolveVideoPointer uses default kind when kind is missing or invalid

ok 9 - resolveVideoPointer uses default kind when kind is missing or invalid

duration_ms: 2.052097 type: 'test' ...

Subtest: resolveVideoPointer returns null for invalid inputs

ok 10 - resolveVideoPointer returns null for invalid inputs

duration_ms: 1.662151 type: 'test' ... 1..10

tests 10

suites 0

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 63.974409

→ Running tests/utils/videoTimestamps.test.mjs TAP version 13

Subtest: getVideoRootIdentifier

# Subtest: returns empty string for invalid inputs
ok 1 - returns empty string for invalid inputs
  ---
  duration_ms: 6.934403
  type: 'test'
  ...
# Subtest: returns videoRootId when present
ok 2 - returns videoRootId when present
  ---
  duration_ms: 1.123935
  type: 'test'
  ...
# Subtest: returns id when videoRootId is missing
ok 3 - returns id when videoRootId is missing
  ---
  duration_ms: 0.968867
  type: 'test'
  ...
# Subtest: returns id when videoRootId is not a string
ok 4 - returns id when videoRootId is not a string
  ---
  duration_ms: 0.933025
  type: 'test'
  ...
1..4

ok 1 - getVideoRootIdentifier

duration_ms: 16.694554 type: 'test' ...

Subtest: applyRootTimestampToVideosMap

# Subtest: returns early if videosMap is not a Map
ok 1 - returns early if videosMap is not a Map
  ---
  duration_ms: 1.26907
  type: 'test'
  ...
# Subtest: updates rootCreatedAt for matching video ID
ok 2 - updates rootCreatedAt for matching video ID
  ---
  duration_ms: 0.874187
  type: 'test'
  ...
# Subtest: updates rootCreatedAt for other videos with same rootId
ok 3 - updates rootCreatedAt for other videos with same rootId
  ---
  duration_ms: 1.32399
  type: 'test'
  ...
# Subtest: skips invalid stored objects in map
ok 4 - skips invalid stored objects in map
  ---
  duration_ms: 1.352556
  type: 'test'
  ...
1..4

ok 2 - applyRootTimestampToVideosMap

duration_ms: 10.784936 type: 'test' ...

Subtest: syncActiveVideoRootTimestamp

# Subtest: returns false for invalid timestamp
ok 1 - returns false for invalid timestamp
  ---
  duration_ms: 1.363772
  type: 'test'
  ...
# Subtest: returns false for invalid activeVideo
ok 2 - returns false for invalid activeVideo
  ---
  duration_ms: 0.529702
  type: 'test'
  ...
# Subtest: returns false if activeVideo has no root identifier
ok 3 - returns false if activeVideo has no root identifier
  ---
  duration_ms: 0.514563
  type: 'test'
  ...
# Subtest: returns false if rootId mismatch
ok 4 - returns false if rootId mismatch
  ---
  duration_ms: 0.920036
  type: 'test'
  ...
# Subtest: returns false if timestamp already matches
ok 5 - returns false if timestamp already matches
  ---
  duration_ms: 0.449004
  type: 'test'
  ...
# Subtest: updates activeVideo and displayTags
ok 6 - updates activeVideo and displayTags
  ---
  duration_ms: 1.560979
  type: 'test'
  ...
# Subtest: calls videoModal.updateMetadata if provided
ok 7 - calls videoModal.updateMetadata if provided
  ---
  duration_ms: 0.489963
  type: 'test'
  ...
1..7

ok 3 - syncActiveVideoRootTimestamp

duration_ms: 9.074519 type: 'test' ... 1..3

tests 18

suites 0

pass 18

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 122.290853

→ Running tests/validate-config.test.mjs TAP version 13

Subtest: validateInstanceConfig succeeds with valid configuration

ok 1 - validateInstanceConfig succeeds with valid configuration

duration_ms: 6.128733 type: 'test' ...

Subtest: validateInstanceConfig throws when platform fee is positive without a platform override

ok 2 - validateInstanceConfig throws when platform fee is positive without a platform override

duration_ms: 2.974546 type: 'test' ...

Subtest: validateInstanceConfig throws when ADMIN_SUPER_NPUB is empty

ok 3 - validateInstanceConfig throws when ADMIN_SUPER_NPUB is empty

duration_ms: 1.095738 type: 'test' ...

Subtest: validateInstanceConfig throws when ADMIN_SUPER_NPUB does not start with npub

ok 4 - validateInstanceConfig throws when ADMIN_SUPER_NPUB does not start with npub

duration_ms: 0.711175 type: 'test' ...

Subtest: validateInstanceConfig throws when PLATFORM_FEE_PERCENT is negative

ok 5 - validateInstanceConfig throws when PLATFORM_FEE_PERCENT is negative

duration_ms: 0.584953 type: 'test' ...

Subtest: validateInstanceConfig throws when PLATFORM_FEE_PERCENT is greater than 100

ok 6 - validateInstanceConfig throws when PLATFORM_FEE_PERCENT is greater than 100

duration_ms: 0.792492 type: 'test' ...

Subtest: validateInstanceConfig throws when PLATFORM_FEE_PERCENT is not finite

ok 7 - validateInstanceConfig throws when PLATFORM_FEE_PERCENT is not finite

duration_ms: 0.779622 type: 'test' ...

Subtest: validateInstanceConfig throws when THEME_ACCENT_OVERRIDES has invalid hex color

ok 8 - validateInstanceConfig throws when THEME_ACCENT_OVERRIDES has invalid hex color

duration_ms: 0.882488 type: 'test' ...

Subtest: validateInstanceConfig throws when THEME_ACCENT_OVERRIDES structure is invalid

ok 9 - validateInstanceConfig throws when THEME_ACCENT_OVERRIDES structure is invalid

duration_ms: 1.309166 type: 'test' ...

Subtest: validateInstanceConfig throws when optional URL is invalid

ok 10 - validateInstanceConfig throws when optional URL is invalid

duration_ms: 1.909481 type: 'test' ...

Subtest: validateInstanceConfig throws when optional URL has invalid protocol

ok 11 - validateInstanceConfig throws when optional URL has invalid protocol

duration_ms: 1.02561 type: 'test' ... 1..11

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 89.307555

→ Running tests/video-card-source-visibility.test.mjs TAP version 13

Subtest: updateVideoCardSourceVisibility hides non-owner cards without healthy sources and restores visibility

ok 1 - updateVideoCardSourceVisibility hides non-owner cards without healthy sources and restores visibility

duration_ms: 220.541193 type: 'test' ...

Subtest: VideoCard hides cards without playable sources until a healthy CDN update arrives

ok 2 - VideoCard hides cards without playable sources until a healthy CDN update arrives

duration_ms: 84.473581 type: 'test' ...

Subtest: VideoCard.closeMoreMenu only restores focus when the trigger was expanded

ok 3 - VideoCard.closeMoreMenu only restores focus when the trigger was expanded

duration_ms: 61.422429 type: 'test' ...

Subtest: VideoCard.closeSettingsMenu only restores focus when the trigger was expanded

ok 4 - VideoCard.closeSettingsMenu only restores focus when the trigger was expanded

duration_ms: 55.015894 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 472.79833

→ Running tests/video-modal-accessibility.test.mjs TAP version 13

Subtest: video modal rehydrates and plays through a connected element across views

ok 1 - video modal rehydrates and plays through a connected element across views

duration_ms: 564.019693 type: 'test' ...

Subtest: torrent-only playback resets the modal video before the next hosted session

ok 2 - torrent-only playback resets the modal video before the next hosted session

duration_ms: 34.656875 type: 'test' ... [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close).

Subtest: backdrop data-dismiss closes the modal and restores focus

ok 3 - backdrop data-dismiss closes the modal and restores focus

duration_ms: 760.266179 type: 'test' ...

Subtest: Escape key closes the modal and returns focus to the trigger

ok 4 - Escape key closes the modal and returns focus to the trigger

duration_ms: 320.631564 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 220.638713 type: 'test' ...

Subtest: video modal video shell is not sticky at mobile breakpoints

ok 6 - video modal video shell is not sticky at mobile breakpoints

duration_ms: 100.800857 type: 'test' ...

Subtest: video modal toggles document scroll locking on open/close

ok 7 - video modal toggles document scroll locking on open/close

duration_ms: 198.692404 type: 'test' ...

Subtest: video modal comments region exposes aria landmarks and stays in the focus order

ok 8 - video modal comments region exposes aria landmarks and stays in the focus order

duration_ms: 226.49954 type: 'test' ...

Subtest: video modal zap dialog updates aria state while toggling

ok 9 - video modal zap dialog updates aria state while toggling

duration_ms: 559.882971 type: 'test' ...

Subtest: video modal inherits design system mode when loaded dynamically

ok 10 - video modal inherits design system mode when loaded dynamically

duration_ms: 77.814584 type: 'test' ... 1..10

tests 10

suites 0

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3088.998282

→ Running tests/video-modal-comments.test.mjs TAP version 13

Subtest: video modal rehydrates and plays through a connected element across views

ok 1 - video modal rehydrates and plays through a connected element across views

duration_ms: 673.891336 type: 'test' ...

Subtest: torrent-only playback resets the modal video before the next hosted session

ok 2 - torrent-only playback resets the modal video before the next hosted session

duration_ms: 55.667666 type: 'test' ... [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close).

Subtest: backdrop data-dismiss closes the modal and restores focus

ok 3 - backdrop data-dismiss closes the modal and restores focus

duration_ms: 678.816543 type: 'test' ...

Subtest: Escape key closes the modal and returns focus to the trigger

ok 4 - Escape key closes the modal and returns focus to the trigger

duration_ms: 274.944255 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 229.17407 type: 'test' ...

Subtest: video modal video shell is not sticky at mobile breakpoints

ok 6 - video modal video shell is not sticky at mobile breakpoints

duration_ms: 264.737158 type: 'test' ...

Subtest: video modal toggles document scroll locking on open/close

ok 7 - video modal toggles document scroll locking on open/close

duration_ms: 205.53258 type: 'test' ...

Subtest: video modal comments region exposes aria landmarks and stays in the focus order

ok 8 - video modal comments region exposes aria landmarks and stays in the focus order

duration_ms: 389.225081 type: 'test' ...

Subtest: video modal zap dialog updates aria state while toggling

ok 9 - video modal zap dialog updates aria state while toggling

duration_ms: 828.682592 type: 'test' ...

Subtest: video modal inherits design system mode when loaded dynamically

ok 10 - video modal inherits design system mode when loaded dynamically

duration_ms: 204.441361 type: 'test' ...

Subtest: VideoModal comment section toggles visibility and renders hydrated comments

ok 11 - VideoModal comment section toggles visibility and renders hydrated comments

duration_ms: 228.335345 type: 'test' ...

Subtest: Unauthenticated users can read comments while the composer stays disabled

ok 12 - Unauthenticated users can read comments while the composer stays disabled

duration_ms: 210.819921 type: 'test' ...

Subtest: Guest users render loaded thread snapshots before composer auth enforcement

ok 13 - Guest users render loaded thread snapshots before composer auth enforcement

duration_ms: 200.193057 type: 'test' ...

Subtest: Thread ready snapshots force comments visible before composer gating

ok 14 - Thread ready snapshots force comments visible before composer gating

duration_ms: 605.992506 type: 'test' ...

Subtest: Controller renders synchronous loadThread snapshots immediately

ok 15 - Controller renders synchronous loadThread snapshots immediately

duration_ms: 153.890726 type: 'test' ...

Subtest: VideoModalCommentController attaches profiles using normalized pubkeys

ok 16 - VideoModalCommentController attaches profiles using normalized pubkeys

duration_ms: 3.429533 type: 'test' ...

Subtest: VideoModalCommentController accepts mixed-case videoEventIds for snapshots

ok 17 - VideoModalCommentController accepts mixed-case videoEventIds for snapshots

duration_ms: 1.035316 type: 'test' ...

Subtest: VideoModal comment composer updates messaging and dispatches events

ok 18 - VideoModal comment composer updates messaging and dispatches events

duration_ms: 179.85139 type: 'test' ...

Subtest: VideoModalCommentController load preserves current video for submissions

ok 19 - VideoModalCommentController load preserves current video for submissions

duration_ms: 2.100306 type: 'test' ...

Subtest: VideoModalCommentController falls back to thread pointer when video tags are absent

ok 20 - VideoModalCommentController falls back to thread pointer when video tags are absent

duration_ms: 1.088526 type: 'test' ...

Subtest: VideoModalCommentController preserves videoRootId when only root pointer is provided

ok 21 - VideoModalCommentController preserves videoRootId when only root pointer is provided

duration_ms: 1.65271 type: 'test' ...

Subtest: VideoModalCommentController uses pointerIdentifiers root id fallback

ok 22 - VideoModalCommentController uses pointerIdentifiers root id fallback

duration_ms: 1.227976 type: 'test' ...

Subtest: VideoModalCommentController publishes comment using event id fallback

ok 23 - VideoModalCommentController publishes comment using event id fallback

duration_ms: 0.982044 type: 'test' ...

Subtest: VideoModalCommentController disposes safely during in-flight comment loads

ok 24 - VideoModalCommentController disposes safely during in-flight comment loads

duration_ms: 1.215809 type: 'test' ...

Subtest: VideoModalCommentController prompts login when publish requires authentication

ok 25 - VideoModalCommentController prompts login when publish requires authentication

duration_ms: 1.254197 type: 'test' ...

Subtest: VideoModalCommentController includes parent metadata when replying

ok 26 - VideoModalCommentController includes parent metadata when replying

duration_ms: 0.593327 type: 'test' ...

Subtest: VideoModal comment section exposes aria landmarks and participates in focus trap

ok 27 - VideoModal comment section exposes aria landmarks and participates in focus trap

duration_ms: 261.951619 type: 'test' ... 1..27

tests 27

suites 0

pass 27

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 5790.644266

→ Running tests/video-modal-controller.test.mjs TAP version 13

Subtest: VideoModalController

# Subtest: ensureVideoModalReady throws if modal is missing
ok 1 - ensureVideoModalReady throws if modal is missing
  ---
  duration_ms: 2.572652
  type: 'test'
  ...
# Subtest: ensureVideoModalReady loads modal if needs rehydrate
ok 2 - ensureVideoModalReady loads modal if needs rehydrate
  ---
  duration_ms: 0.856755
  type: 'test'
  ...
# Subtest: showModalWithPoster uses provided video
ok 3 - showModalWithPoster uses provided video
  ---
  duration_ms: 1.583789
  type: 'test'
  ...
# Subtest: showModalWithPoster falls back to current video
ok 4 - showModalWithPoster falls back to current video
  ---
  duration_ms: 1.196012
  type: 'test'
  ...
# Subtest: forceRemoveModalPoster calls modal method
ok 5 - forceRemoveModalPoster calls modal method
  ---
  duration_ms: 0.362717
  type: 'test'
  ...
1..5

ok 1 - VideoModalController

duration_ms: 9.918748 type: 'test' ... 1..1

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 35.788631

→ Running tests/video-modal-controllers.test.mjs TAP version 13

Subtest: video modal rehydrates and plays through a connected element across views

ok 1 - video modal rehydrates and plays through a connected element across views

duration_ms: 631.532286 type: 'test' ...

Subtest: torrent-only playback resets the modal video before the next hosted session

ok 2 - torrent-only playback resets the modal video before the next hosted session

duration_ms: 50.351092 type: 'test' ... [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close).

Subtest: backdrop data-dismiss closes the modal and restores focus

ok 3 - backdrop data-dismiss closes the modal and restores focus

duration_ms: 985.474662 type: 'test' ...

Subtest: Escape key closes the modal and returns focus to the trigger

ok 4 - Escape key closes the modal and returns focus to the trigger

duration_ms: 254.160763 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 150.469936 type: 'test' ...

Subtest: video modal video shell is not sticky at mobile breakpoints

ok 6 - video modal video shell is not sticky at mobile breakpoints

duration_ms: 194.969526 type: 'test' ...

Subtest: video modal toggles document scroll locking on open/close

ok 7 - video modal toggles document scroll locking on open/close

duration_ms: 240.366196 type: 'test' ...

Subtest: video modal comments region exposes aria landmarks and stays in the focus order

ok 8 - video modal comments region exposes aria landmarks and stays in the focus order

duration_ms: 266.202965 type: 'test' ...

Subtest: video modal zap dialog updates aria state while toggling

ok 9 - video modal zap dialog updates aria state while toggling

duration_ms: 710.214977 type: 'test' ...

Subtest: video modal inherits design system mode when loaded dynamically

ok 10 - video modal inherits design system mode when loaded dynamically

duration_ms: 188.967178 type: 'test' ...

Subtest: CommentsController lifecycle manages comment references

ok 11 - CommentsController lifecycle manages comment references

duration_ms: 159.848787 type: 'test' ...

Subtest: ReactionsController delegates reaction updates

ok 12 - ReactionsController delegates reaction updates

duration_ms: 167.515726 type: 'test' ...

Subtest: SimilarContentController toggles section visibility

ok 13 - SimilarContentController toggles section visibility

duration_ms: 153.907515 type: 'test' ...

Subtest: ModerationController reset clears moderation overlay references

ok 14 - ModerationController reset clears moderation overlay references

duration_ms: 188.707734 type: 'test' ...

Subtest: VideoModal trims tag strip to fit modal width

ok 15 - VideoModal trims tag strip to fit modal width

duration_ms: 299.859157 type: 'test' ... 1..15

tests 15

suites 0

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 4781.980798

→ Running tests/video-modal-moderation.test.mjs TAP version 13

Subtest: video modal rehydrates and plays through a connected element across views

ok 1 - video modal rehydrates and plays through a connected element across views

duration_ms: 765.529172 type: 'test' ...

Subtest: torrent-only playback resets the modal video before the next hosted session

ok 2 - torrent-only playback resets the modal video before the next hosted session

duration_ms: 45.939528 type: 'test' ... [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close).

Subtest: backdrop data-dismiss closes the modal and restores focus

ok 3 - backdrop data-dismiss closes the modal and restores focus

duration_ms: 1200.106835 type: 'test' ...

Subtest: Escape key closes the modal and returns focus to the trigger

ok 4 - Escape key closes the modal and returns focus to the trigger

duration_ms: 370.451638 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 527.832526 type: 'test' ...

Subtest: video modal video shell is not sticky at mobile breakpoints

ok 6 - video modal video shell is not sticky at mobile breakpoints

duration_ms: 372.428172 type: 'test' ...

Subtest: video modal toggles document scroll locking on open/close

ok 7 - video modal toggles document scroll locking on open/close

duration_ms: 289.470414 type: 'test' ...

Subtest: video modal comments region exposes aria landmarks and stays in the focus order

ok 8 - video modal comments region exposes aria landmarks and stays in the focus order

duration_ms: 392.479054 type: 'test' ...

Subtest: video modal zap dialog updates aria state while toggling

ok 9 - video modal zap dialog updates aria state while toggling

duration_ms: 861.516975 type: 'test' ...

Subtest: video modal inherits design system mode when loaded dynamically

ok 10 - video modal inherits design system mode when loaded dynamically

duration_ms: 240.152338 type: 'test' ...

Subtest: VideoModal blurs and restores playback when moderation overlay toggles

# Subtest: trusted reports blur the active video
ok 1 - trusted reports blur the active video
  ---
  duration_ms: 19.275278
  type: 'test'
  ...
# Subtest: trusted mute blur state matches moderation context
ok 2 - trusted mute blur state matches moderation context
  ---
  duration_ms: 7.583324
  type: 'test'
  ...
1..2

ok 11 - VideoModal blurs and restores playback when moderation overlay toggles

duration_ms: 330.899134 type: 'test' ... 1..11

tests 13

suites 0

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 5436.565244

→ Running tests/video-modal-tags.test.mjs TAP version 13

Subtest: video modal rehydrates and plays through a connected element across views

ok 1 - video modal rehydrates and plays through a connected element across views

duration_ms: 746.633691 type: 'test' ...

Subtest: torrent-only playback resets the modal video before the next hosted session

ok 2 - torrent-only playback resets the modal video before the next hosted session

duration_ms: 66.859985 type: 'test' ... [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close). [VideoModal] Cleared loading poster (close).

Subtest: backdrop data-dismiss closes the modal and restores focus

ok 3 - backdrop data-dismiss closes the modal and restores focus

duration_ms: 749.869226 type: 'test' ...

Subtest: Escape key closes the modal and returns focus to the trigger

ok 4 - Escape key closes the modal and returns focus to the trigger

duration_ms: 329.621804 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 244.422265 type: 'test' ...

Subtest: video modal video shell is not sticky at mobile breakpoints

ok 6 - video modal video shell is not sticky at mobile breakpoints

duration_ms: 219.291224 type: 'test' ...

Subtest: video modal toggles document scroll locking on open/close

ok 7 - video modal toggles document scroll locking on open/close

duration_ms: 322.32707 type: 'test' ...

Subtest: video modal comments region exposes aria landmarks and stays in the focus order

ok 8 - video modal comments region exposes aria landmarks and stays in the focus order

duration_ms: 143.532439 type: 'test' ...

Subtest: video modal zap dialog updates aria state while toggling

ok 9 - video modal zap dialog updates aria state while toggling

duration_ms: 804.384222 type: 'test' ...

Subtest: video modal inherits design system mode when loaded dynamically

ok 10 - video modal inherits design system mode when loaded dynamically

duration_ms: 108.9771 type: 'test' ...

Subtest: VideoModal renders tag metadata and toggles visibility

ok 11 - VideoModal renders tag metadata and toggles visibility

duration_ms: 162.737728 type: 'test' ... 1..11

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3931.834789

→ Running tests/video-note-payload.test.mjs TAP version 13

Subtest: normalizes minimal payload with hosted URL

ok 1 - normalizes minimal payload with hosted URL

duration_ms: 7.019507 type: 'test' ...

Subtest: normalizes mode and boolean flags

ok 2 - normalizes mode and boolean flags

duration_ms: 1.063242 type: 'test' ...

Subtest: augments magnets with ws/xs hints

ok 3 - augments magnets with ws/xs hints

duration_ms: 4.406197 type: 'test' ...

Subtest: normalizes nip71 metadata collections

ok 4 - normalizes nip71 metadata collections

duration_ms: 3.423213 type: 'test' ...

Subtest: derives legacy duration fallback from imeta variants

ok 5 - derives legacy duration fallback from imeta variants

duration_ms: 0.893136 type: 'test' ...

Subtest: reports validation errors for missing fields

ok 6 - reports validation errors for missing fields

duration_ms: 0.646738 type: 'test' ...

Subtest: rejects insecure hosted URLs

ok 7 - rejects insecure hosted URLs

duration_ms: 1.018018 type: 'test' ...

Subtest: allows publishing with only imeta variants

ok 8 - allows publishing with only imeta variants

duration_ms: 0.687849 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 54.193317

→ Running tests/video-schema-and-conversion.test.mjs TAP version 13

Subtest: video post schema documents nsfw and kids flags

ok 1 - video post schema documents nsfw and kids flags

duration_ms: 3.64297 type: 'test' ...

Subtest: convertEventToVideo normalizes nsfw and kids booleans

ok 2 - convertEventToVideo normalizes nsfw and kids booleans

duration_ms: 1.969638 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.912355

→ Running tests/video-settings-menu-controller.test.mjs TAP version 13

Subtest: VideoSettingsMenuController - requestMenu opens popover

ok 1 - VideoSettingsMenuController - requestMenu opens popover

duration_ms: 6.485272 type: 'test' ...

Subtest: VideoSettingsMenuController - closeMenu closes popover

ok 2 - VideoSettingsMenuController - closeMenu closes popover

duration_ms: 2.140673 type: 'test' ...

Subtest: VideoSettingsMenuController - requestMenu toggles if open

ok 3 - VideoSettingsMenuController - requestMenu toggles if open

duration_ms: 2.573714 type: 'test' ...

Subtest: VideoSettingsMenuController - closeAll closes all popovers

ok 4 - VideoSettingsMenuController - closeAll closes all popovers

duration_ms: 2.797512 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 62.508334

→ Running tests/webtorrent-handlers.test.mjs TAP version 13

Subtest: TorrentClient Handlers

# Subtest: handleTorrentStream (chrome) should set up video correctly
ok 1 - handleTorrentStream (chrome) should set up video correctly
  ---
  duration_ms: 48.743037
  type: 'test'
  ...
ok 2 - handleTorrentStream (firefox) should set up video correctly with highWaterMark
duration_ms: 55.704919
type: 'test'
...
# Subtest: handleTorrentStream (chrome) should handle CORS warning logic
ok 3 - handleTorrentStream (chrome) should handle CORS warning logic
---
duration_ms: 2.521307
type: 'test'
...
1..3
ok 1 - TorrentClient Handlers

[bitvid] CORS warning detected. Attempting to remove the failing webseed/tracker. [bitvid] Cleaned up webseeds => [ 'http://good.com/video.mp4' ] [bitvid] Cleaned up trackers => [ 'ws://good.com' ] # Subtest: handleTorrentStream (firefox) should set up video correctly with highWaterMark

duration_ms: 111.475367 type: 'suite' ... 1..1

tests 3

suites 1

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 129.221912

→ Running tests/webtorrent-regression.test.mjs TAP version 13

Subtest: WebTorrent Regression Tests

# Subtest: probePeers should report healthy if webseed is present even with 0 peers
ok 1 - probePeers should report healthy if webseed is present even with 0 peers
  ---
  duration_ms: 9.134921
  type: 'test'
  ...
# Subtest: probePeers should report unhealthy if no webseed and 0 peers
ok 2 - probePeers should report unhealthy if no webseed and 0 peers
  ---
  duration_ms: 51.663067
  type: 'test'
  ...
1..2

ok 1 - WebTorrent Regression Tests

duration_ms: 65.118071 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 78.850611

✔ All unit tests passed Unit tests exit code: 0