Files
bitvid/test_logs/TEST_LOG_2026-02-16.md
T

279 KiB

Test Logs for 2026-02-16

Running npm ci...

[email protected] prepare npm run build:css

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

added 424 packages, and audited 425 packages in 19s

108 packages are looking for funding run npm fund for details

found 0 vulnerabilities

added 5 packages, removed 2 packages, changed 8 packages, and audited 428 packages in 4s

110 packages are looking for funding run npm fund for details

found 0 vulnerabilities

[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: 4.01842 type: 'test' ...

Subtest: batchFetchProfiles respects forceRefresh

ok 2 - batchFetchProfiles respects forceRefresh

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.781695

→ 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.637879
  type: 'test'
  ...
# Subtest: setPubkey() updates state and notifies subscribers
ok 2 - setPubkey() updates state and notifies subscribers
  ---
  duration_ms: 1.084388
  type: 'test'
  ...
# Subtest: setCurrentUserNpub() updates state
ok 3 - setCurrentUserNpub() updates state
  ---
  duration_ms: 0.507429
  type: 'test'
  ...
# Subtest: setCurrentVideo() updates state
ok 4 - setCurrentVideo() updates state
  ---
  duration_ms: 0.571966
  type: 'test'
  ...
# Subtest: setVideosMap() updates state
ok 5 - setVideosMap() updates state
  ---
  duration_ms: 1.06145
  type: 'test'
  ...
# Subtest: setVideoSubscription() updates state
ok 6 - setVideoSubscription() updates state
  ---
  duration_ms: 0.578872
  type: 'test'
  ...
# Subtest: setModalState() updates state and notifies modal subscribers
ok 7 - setModalState() updates state and notifies modal subscribers
  ---
  duration_ms: 0.979166
  type: 'test'
  ...
# Subtest: Global subscriber receives updates
ok 8 - Global subscriber receives updates
  ---
  duration_ms: 0.68527
  type: 'test'
  ...
# Subtest: resetAppState() clears all state and notifies
ok 9 - resetAppState() clears all state and notifies
  ---
  duration_ms: 1.22186
  type: 'test'
  ...
1..9

ok 1 - AppState

duration_ms: 11.900583 type: 'suite' ... 1..1

tests 9

suites 1

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.748088

→ 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: 147.202064 type: 'test' ...

Subtest: renderChannelVideosFromList applies moderation blur without existing metadata

ok 2 - renderChannelVideosFromList applies moderation blur without existing metadata

duration_ms: 45.476705 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: 14.982635 type: 'test' ...

Subtest: moderation override clears channel blur via event wiring

ok 4 - moderation override clears channel blur via event wiring

duration_ms: 32.561716 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: 28.986907 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: 31.604459 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 318.379292

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

Subtest: createFeedCoordinator - loadForYouVideos

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

ok 1 - createFeedCoordinator - loadForYouVideos

duration_ms: 4.538199 type: 'test' ... 1..1

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.983581

→ 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: 90.025969 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: 13.859391 type: 'test' ...

Subtest: handleHashChange respects explicit view regardless of login state

ok 3 - handleHashChange respects explicit view regardless of login state

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 132.224508

→ 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: 96.263846 type: 'test' ...

Subtest: hydrateSidebarNavigation hides chrome controls for logged-out viewers

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

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 123.002063

→ 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.358487 type: 'test' ...

Subtest: isUserLoggedIn treats extension logins as authenticated

ok 2 - isUserLoggedIn treats extension logins as authenticated

duration_ms: 0.338155 type: 'test' ...

Subtest: isUserLoggedIn guards against mismatched nostrClient state

ok 3 - isUserLoggedIn guards against mismatched nostrClient state

duration_ms: 0.210792 type: 'test' ...

Subtest: isUserLoggedIn ignores anonymous session actor mismatches

ok 4 - isUserLoggedIn ignores anonymous session actor mismatches

duration_ms: 0.269272 type: 'test' ...

Subtest: isUserLoggedIn rejects mismatched managed session actors

ok 5 - isUserLoggedIn rejects mismatched managed session actors

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.142902

→ 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: 6.005706 type: 'test' ...

Subtest: handleModerationOverride resumes deferred playback

ok 2 - handleModerationOverride resumes deferred playback

duration_ms: 1.244395 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: 3.720907 type: 'test' ...

Subtest: handleModerationBlock returns false when viewer is logged out

ok 4 - handleModerationBlock returns false when viewer is logged out

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.129108

→ 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: 4.545188 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.323418

→ 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.44956 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.596169 type: 'test' ...

Subtest: skips candidates without tag metadata

ok 3 - skips candidates without tag metadata

duration_ms: 0.365263 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.604417 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.501604

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

Subtest: AuthService

# Subtest: applyPostLoginState success flow
ok 1 - applyPostLoginState success flow
  ---
  duration_ms: 14.515251
  type: 'test'
  ...
# Subtest: applyPostLoginState with deferBlocks: true
ok 2 - applyPostLoginState with deferBlocks: true
  ---
  duration_ms: 10.738254
  type: 'test'
  ...
# Subtest: applyPostLoginState partial failure
ok 3 - applyPostLoginState partial failure
  ---
  duration_ms: 1.601171
  type: 'test'
  ...
# Subtest: applyPostLoginState without pubkey
ok 4 - applyPostLoginState without pubkey
  ---
  duration_ms: 0.705631
  type: 'test'
  ...
# Subtest: login mocks postLogin flow
ok 5 - login mocks postLogin flow
  ---
  duration_ms: 3.151055
  type: 'test'
  ...
# Subtest: login fails on lockdown
ok 6 - login fails on lockdown
  ---
  duration_ms: 1.029846
  type: 'test'
  ...
# Subtest: login succeeds for admin on lockdown
ok 7 - login succeeds for admin on lockdown
  ---
  duration_ms: 1.369958
  type: 'test'
  ...
# Subtest: loadOwnProfile
    # Subtest: Fast Relay Success
    ok 1 - Fast Relay Success
      ---
      duration_ms: 1.325011
      type: 'test'
      ...
    # Subtest: All Relays Fail
    ok 2 - All Relays Fail
      ---
      duration_ms: 1.279888
      type: 'test'
      ...
    # Subtest: Timeout Fallback
    ok 3 - Timeout Fallback
      ---
      duration_ms: 2502.454327
      type: 'test'
      ...
    1..3
ok 8 - loadOwnProfile
  ---
  duration_ms: 2505.86568
  type: 'suite'
  ...
1..8

ok 1 - AuthService

duration_ms: 2540.985359 type: 'suite' ... 1..1

tests 10

suites 2

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2549.52559

→ 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.757185
  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.643923
  type: 'test'
  ...
# Subtest: throws error if no extension available
ok 3 - throws error if no extension available
  ---
  duration_ms: 1.326058
  type: 'test'
  ...
# Subtest: prioritizes explicit extension over window.nostr
ok 4 - prioritizes explicit extension over window.nostr
  ---
  duration_ms: 0.836377
  type: 'test'
  ...
1..4

ok 1 - createNip07SigningAdapter

duration_ms: 8.262425 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.563094

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

Subtest: AuthService Coverage

# Subtest: login
    # Subtest: successfully logs in with valid pubkey
    ok 1 - successfully logs in with valid pubkey
      ---
      duration_ms: 6.886312
      type: 'test'
      ...
    # Subtest: updates global state and calls post-login hooks
    ok 2 - updates global state and calls post-login hooks
      ---
      duration_ms: 1.52451
      type: 'test'
      ...
    # Subtest: throws if lockdown is active and user is not admin
    ok 3 - throws if lockdown is active and user is not admin
      ---
      duration_ms: 1.848597
      type: 'test'
      ...
    # Subtest: allows login if lockdown is active but user is admin
    ok 4 - allows login if lockdown is active but user is admin
      ---
      duration_ms: 1.181426
      type: 'test'
      ...
    1..4
ok 1 - login
  ---
  duration_ms: 13.042229
  type: 'suite'
  ...
# Subtest: logout
    # Subtest: clears state and calls reset on dependencies
    ok 1 - clears state and calls reset on dependencies
      ---
      duration_ms: 2.140077
      type: 'test'
      ...
    1..1
ok 2 - logout
  ---
  duration_ms: 2.571073
  type: 'suite'
  ...
# Subtest: requestLogin
    # Subtest: uses provider to login
    ok 1 - uses provider to login
      ---
      duration_ms: 1.315193
      type: 'test'
      ...
    # Subtest: does not call login if autoApply is false
    ok 2 - does not call login if autoApply is false
      ---
      duration_ms: 0.802439
      type: 'test'
      ...
    1..2
ok 3 - requestLogin
  ---
  duration_ms: 2.422675
  type: 'suite'
  ...
# Subtest: switchProfile
    # Subtest: switches to an existing profile
    ok 1 - switches to an existing profile
      ---
      duration_ms: 3.463541
      type: 'test'
      ...
    # Subtest: reorders saved profiles on switch
    ok 2 - reorders saved profiles on switch
      ---
      duration_ms: 2.499656
      type: 'test'
      ...
    1..2
ok 4 - switchProfile
  ---
  duration_ms: 6.205295
  type: 'suite'
  ...
1..4

ok 1 - AuthService Coverage

duration_ms: 25.498607 type: 'suite' ... 1..1

tests 9

suites 5

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 43.799416

→ 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: 3.044692 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.493314 type: 'test' ...

Subtest: buildCommentEvent normalizes relays and preserves explicit overrides

ok 3 - buildCommentEvent normalizes relays and preserves explicit overrides

duration_ms: 0.813665 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.281288 type: 'test' ...

Subtest: buildCommentEvent accepts partial metadata for parent overrides

ok 5 - buildCommentEvent accepts partial metadata for parent overrides

duration_ms: 0.424866 type: 'test' ...

Subtest: buildCommentEvent sanitizes additional tags before signing

ok 6 - buildCommentEvent sanitizes additional tags before signing

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.480125

→ 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.451471 type: 'test' ...

Subtest: CommentThreadService surfaces cache read failures

ok 2 - CommentThreadService surfaces cache read failures

duration_ms: 0.773296 type: 'test' ...

Subtest: CommentThreadService surfaces cache write failures

ok 3 - CommentThreadService surfaces cache write failures

duration_ms: 0.468377 type: 'test' ...

Subtest: CommentThreadService persists caches safely during teardown failures

ok 4 - CommentThreadService persists caches safely during teardown failures

duration_ms: 1.809545 type: 'test' ...

Subtest: CommentThreadService logs cache usage and fallback decisions

ok 5 - CommentThreadService logs cache usage and fallback decisions

duration_ms: 1.334692 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.089179 type: 'test' ...

Subtest: CommentThreadService normalizes mixed-case pubkeys during hydration

ok 7 - CommentThreadService normalizes mixed-case pubkeys during hydration

duration_ms: 1.216243 type: 'test' ...

Subtest: CommentThreadService deduplicates mixed-case event ids and pubkeys

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

duration_ms: 1.115588 type: 'test' ...

Subtest: CommentThreadService retries profile hydration before succeeding

ok 9 - CommentThreadService retries profile hydration before succeeding

duration_ms: 77.03844 type: 'test' ...

Subtest: CommentThreadService surfaces profile hydration failures after retries

ok 10 - CommentThreadService surfaces profile hydration failures after retries

duration_ms: 201.810724 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: 7.539646 type: 'test' ...

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

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

duration_ms: 35.550535 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: 1.643731 type: 'test' ...

Subtest: CommentThreadService requests and snapshots comments by root identifier

ok 14 - CommentThreadService requests and snapshots comments by root identifier

duration_ms: 1.760149 type: 'test' ...

Subtest: CommentThreadService falls back to pointerIdentifiers root id

ok 15 - CommentThreadService falls back to pointerIdentifiers root id

duration_ms: 1.309128 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: 9.585084 type: 'test' ...

Subtest: CommentThreadService teardown cancels hydration timers

ok 17 - CommentThreadService teardown cancels hydration timers

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

tests 17

suites 0

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 441.271587

→ 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: 23.22394
  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.620548
  type: 'test'
  ...
1..2

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

duration_ms: 116.21281 type: 'test' ...

Subtest: NIP-04/44 Compliance: Decryption Fallback

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

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

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 204.996037

→ 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.439712
  type: 'test'
  ...
# Subtest: runNip07WithRetry retries on timeout error
ok 2 - runNip07WithRetry retries on timeout error
  ---
  duration_ms: 20.479054
  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.482074
  type: 'test'
  ...
1..3

ok 1 - NIP-07 Compliance: Retry Logic

duration_ms: 26.540698 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 60.483448

→ 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: 8.413927
  type: 'test'
  ...
# Subtest: loadRelayList parses r tags correctly
ok 2 - loadRelayList parses r tags correctly
  ---
  duration_ms: 0.985872
  type: 'test'
  ...
1..2

ok 1 - NIP-65 Compliance: Relay List Loading

duration_ms: 182.069901 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 197.98346

→ 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.807829
  type: 'test'
  ...
# Subtest: prepareVideoPublishPayload includes NIP-71 tags when provided
ok 2 - prepareVideoPublishPayload includes NIP-71 tags when provided
  ---
  duration_ms: 1.083481
  type: 'test'
  ...
1..2

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

duration_ms: 46.331627 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 59.602005

→ 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: 392.666207 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 407.84542

→ 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: 109.44923
      type: 'test'
      ...
    # Subtest: should create a style element or use adoptedStyleSheets
    ok 2 - should create a style element or use adoptedStyleSheets
      ---
      duration_ms: 17.355053
      type: 'test'
      ...
    # Subtest: should insert rules with the correct selector
    ok 3 - should insert rules with the correct selector
      ---
      duration_ms: 14.264298
      type: 'test'
      ...
    # Subtest: should handle "&" in selectors
    ok 4 - should handle "&" in selectors
      ---
      duration_ms: 11.458493
      type: 'test'
      ...
    1..4
ok 1 - registerScope
  ---
  duration_ms: 153.848522
  type: 'suite'
  ...
# Subtest: setVariables
    # Subtest: should update CSS variables on the scope
    ok 1 - should update CSS variables on the scope
      ---
      duration_ms: 12.98051
      type: 'test'
      ...
    # Subtest: should handle removing variables
    ok 2 - should handle removing variables
      ---
      duration_ms: 17.593718
      type: 'test'
      ...
    1..2
ok 2 - setVariables
  ---
  duration_ms: 30.937447
  type: 'suite'
  ...
# Subtest: releaseScope
    # Subtest: should remove rules and clean up
    ok 1 - should remove rules and clean up
      ---
      duration_ms: 11.017123
      type: 'test'
      ...
    # Subtest: should return false for non-existent scope
    ok 2 - should return false for non-existent scope
      ---
      duration_ms: 8.685664
      type: 'test'
      ...
    1..2
ok 3 - releaseScope
  ---
  duration_ms: 20.161695
  type: 'suite'
  ...
# Subtest: Isolation
    # Subtest: should manage scopes independently for different documents
    ok 1 - should manage scopes independently for different documents
      ---
      duration_ms: 30.921658
      type: 'test'
      ...
    1..1
ok 4 - Isolation
  ---
  duration_ms: 31.085196
  type: 'suite'
  ...
# Subtest: Edge cases
    # Subtest: should handle invalid selectors gracefully
    ok 1 - should handle invalid selectors gracefully
      ---
      duration_ms: 11.560591
      type: 'test'
      ...
    # Subtest: should handle missing document gracefully
    ok 2 - should handle missing document gracefully
      ---
      duration_ms: 7.146808
      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: 16.195195
      type: 'test'
      ...
    1..3
ok 5 - Edge cases
  ---
  duration_ms: 35.30176
  type: 'suite'
  ...
1..5

ok 1 - js/designSystem/dynamicStyles.js

duration_ms: 272.738686 type: 'suite' ... 1..1

tests 12

suites 6

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 302.140719

→ 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: 2.060908
      type: 'test'
      ...
    # Subtest: should clear checker when passed null
    ok 2 - should clear checker when passed null
      ---
      duration_ms: 0.472591
      type: 'test'
      ...
    # Subtest: should clear checker when passed non-function
    ok 3 - should clear checker when passed non-function
      ---
      duration_ms: 0.932498
      type: 'test'
      ...
    1..3
ok 1 - setDmBlockChecker
  ---
  duration_ms: 5.120161
  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.604427
      type: 'test'
      ...
    # Subtest: should allow messages from non-blocked senders
    ok 2 - should allow messages from non-blocked senders
      ---
      duration_ms: 3.873156
      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: 1.025921
      type: 'test'
      ...
    # Subtest: should block outgoing messages to blocked recipients
    ok 4 - should block outgoing messages to blocked recipients
      ---
      duration_ms: 0.529935
      type: 'test'
      ...
    1..4
ok 2 - applyDirectMessage with block checker
  ---
  duration_ms: 7.848987
  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.703662
      type: 'test'
      ...
    # Subtest: should return true for blocked remote pubkey
    ok 2 - should return true for blocked remote pubkey
      ---
      duration_ms: 0.785261
      type: 'test'
      ...
    # Subtest: should return false for allowed remote pubkey
    ok 3 - should return false for allowed remote pubkey
      ---
      duration_ms: 0.537504
      type: 'test'
      ...
    # Subtest: should handle checker that throws
    ok 4 - should handle checker that throws
      ---
      duration_ms: 2.43674
      type: 'test'
      ...
    1..4
ok 3 - _isDmRemoteBlocked
  ---
  duration_ms: 4.915394
  type: 'suite'
  ...
1..3

ok 1 - DM block/mute filtering

duration_ms: 19.016058 type: 'suite' ... 1..1

tests 11

suites 4

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 42.084243

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

Subtest: writeMessages normalizes records before storing

ok 1 - writeMessages normalizes records before storing

duration_ms: 16.910573 type: 'test' ...

Subtest: updateConversationFromMessage persists metadata updates

ok 2 - updateConversationFromMessage persists metadata updates

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.736534

→ 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: 80.999032 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: 24.07688 type: 'test' ...

Subtest: decryptDM unwraps kind 1059 gift wraps with nip44

ok 3 - decryptDM unwraps kind 1059 gift wraps with nip44

duration_ms: 59.545737 type: 'test' ...

Subtest: decryptDM returns failure when decryptors are unavailable

ok 4 - decryptDM returns failure when decryptors are unavailable

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 179.123323

→ 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: 20.831844 type: 'test' ...

Subtest: direct message list dedupes entries by event id

ok 2 - direct message list dedupes entries by event id

duration_ms: 7.481579 type: 'test' ...

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

Subtest: Documentation Accuracy Verification

# Subtest: setup
ok 1 - setup
  ---
  duration_ms: 7.898501
  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.664375
  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.439054
  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.380457
  type: 'test'
  ...
1..4

ok 1 - Documentation Accuracy Verification

duration_ms: 11.272613 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.582266

→ 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: 291.840198 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: 102.067073 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: 85.402232 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 493.600196

→ 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.287885
  type: 'test'
  ...
# Subtest: should validate buildVideoMirrorEvent
ok 2 - should validate buildVideoMirrorEvent
  ---
  duration_ms: 0.39451
  type: 'test'
  ...
# Subtest: should validate buildRepostEvent
ok 3 - should validate buildRepostEvent
  ---
  duration_ms: 0.432489
  type: 'test'
  ...
# Subtest: should validate buildShareEvent
ok 4 - should validate buildShareEvent
  ---
  duration_ms: 0.63998
  type: 'test'
  ...
# Subtest: should validate buildRelayListEvent
ok 5 - should validate buildRelayListEvent
  ---
  duration_ms: 0.611305
  type: 'test'
  ...
# Subtest: should validate buildDmRelayListEvent
ok 6 - should validate buildDmRelayListEvent
  ---
  duration_ms: 0.402754
  type: 'test'
  ...
# Subtest: should validate buildProfileMetadataEvent
ok 7 - should validate buildProfileMetadataEvent
  ---
  duration_ms: 0.452458
  type: 'test'
  ...
# Subtest: should validate buildMuteListEvent
ok 8 - should validate buildMuteListEvent
  ---
  duration_ms: 0.440811
  type: 'test'
  ...
# Subtest: should validate buildDeletionEvent
ok 9 - should validate buildDeletionEvent
  ---
  duration_ms: 0.747336
  type: 'test'
  ...
# Subtest: should validate buildLegacyDirectMessageEvent
ok 10 - should validate buildLegacyDirectMessageEvent
  ---
  duration_ms: 0.446433
  type: 'test'
  ...
# Subtest: should validate buildDmAttachmentEvent
ok 11 - should validate buildDmAttachmentEvent
  ---
  duration_ms: 0.304664
  type: 'test'
  ...
# Subtest: should validate buildDmReadReceiptEvent
ok 12 - should validate buildDmReadReceiptEvent
  ---
  duration_ms: 0.258794
  type: 'test'
  ...
# Subtest: should validate buildDmTypingIndicatorEvent
ok 13 - should validate buildDmTypingIndicatorEvent
  ---
  duration_ms: 0.342769
  type: 'test'
  ...
# Subtest: should validate buildViewEvent
ok 14 - should validate buildViewEvent
  ---
  duration_ms: 0.42412
  type: 'test'
  ...
# Subtest: should validate buildZapRequestEvent
ok 15 - should validate buildZapRequestEvent
  ---
  duration_ms: 0.32633
  type: 'test'
  ...
# Subtest: should validate buildReactionEvent
ok 16 - should validate buildReactionEvent
  ---
  duration_ms: 0.79048
  type: 'test'
  ...
# Subtest: should validate buildCommentEvent
ok 17 - should validate buildCommentEvent
  ---
  duration_ms: 0.786308
  type: 'test'
  ...
# Subtest: should validate buildWatchHistoryEvent
ok 18 - should validate buildWatchHistoryEvent
  ---
  duration_ms: 0.618208
  type: 'test'
  ...
# Subtest: should validate buildSubscriptionListEvent
ok 19 - should validate buildSubscriptionListEvent
  ---
  duration_ms: 0.405829
  type: 'test'
  ...
# Subtest: should validate buildBlockListEvent
ok 20 - should validate buildBlockListEvent
  ---
  duration_ms: 0.38134
  type: 'test'
  ...
# Subtest: should validate buildHashtagPreferenceEvent
ok 21 - should validate buildHashtagPreferenceEvent
  ---
  duration_ms: 0.196455
  type: 'test'
  ...
# Subtest: should validate buildAdminListEvent (moderation)
ok 22 - should validate buildAdminListEvent (moderation)
  ---
  duration_ms: 0.395626
  type: 'test'
  ...
1..22

ok 1 - Nostr Event Schemas

duration_ms: 14.803496 type: 'suite' ... 1..1

tests 22

suites 1

pass 22

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.447811

→ 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.361468 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: 0.996569 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.978974

→ 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.83095 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.960186 type: 'test' ...

Subtest: explore scorer rewards novelty and new tag fraction

ok 3 - explore scorer rewards novelty and new tag fraction

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.676481

→ 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: 4.697327 type: 'test' ...

Subtest: kids scorer picks educational-boost when dominant

ok 2 - kids scorer picks educational-boost when dominant

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.127539

→ 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: 3.476139 type: 'test' ...

Subtest: tag preference stage accepts interests from nip71 hashtags

ok 2 - tag preference stage accepts interests from nip71 hashtags

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.147912

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

Subtest: prioritizeEntries input validation

ok 1 - prioritizeEntries input validation

duration_ms: 82.1962 type: 'test' ...

Subtest: prioritizeEntries filtering

ok 2 - prioritizeEntries filtering

duration_ms: 13.206691 type: 'test' ...

Subtest: prioritizeEntries without viewport center

ok 3 - prioritizeEntries without viewport center

duration_ms: 10.898592 type: 'test' ...

Subtest: prioritizeEntries with viewport center

ok 4 - prioritizeEntries with viewport center

duration_ms: 10.880382 type: 'test' ...

Subtest: prioritizeEntries ratio prioritization when distances are similar

ok 5 - prioritizeEntries ratio prioritization when distances are similar

duration_ms: 11.261501 type: 'test' ...

Subtest: prioritizeEntries uses boundingClientRect fallback

ok 6 - prioritizeEntries uses boundingClientRect fallback

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 162.732108

→ 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: 23.395768 type: 'test' ...

Subtest: load falls back to nip04 decryption

ok 2 - load falls back to nip04 decryption

duration_ms: 2.738835 type: 'test' ...

Subtest: load retries decryptors when hinted scheme fails

ok 3 - load retries decryptors when hinted scheme fails

duration_ms: 2.409675 type: 'test' ...

Subtest: load defers permission-required decrypts until explicitly enabled

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

duration_ms: 6.739558 type: 'test' ...

Subtest: interest and disinterest lists remain exclusive

ok 5 - interest and disinterest lists remain exclusive

duration_ms: 14.976467 type: 'test' ...

Subtest: publish encrypts payload and builds event via builder

ok 6 - publish encrypts payload and builds event via builder

duration_ms: 2.687623 type: 'test' ...

Subtest: publish falls back to window nostr encryptors

ok 7 - publish falls back to window nostr encryptors

duration_ms: 2.853049 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.575641 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.101487 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: 2.005602 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: 225.270313 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: 63.016725 type: 'test' ...

Subtest: LoginModalController shows NIP-46 handshake panel

ok 3 - LoginModalController shows NIP-46 handshake panel

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 361.784617

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

Subtest: safeDecodeMagnet handles encoded values

ok 1 - safeDecodeMagnet handles encoded values

duration_ms: 1.587369 type: 'test' ...

Subtest: safeDecodeMagnet leaves plain strings untouched

ok 2 - safeDecodeMagnet leaves plain strings untouched

duration_ms: 0.367061 type: 'test' ...

Subtest: bare hashes normalize consistently

ok 3 - bare hashes normalize consistently

duration_ms: 2.742712 type: 'test' ...

Subtest: legacy %3A payloads decode

ok 4 - legacy %3A payloads decode

duration_ms: 0.622437 type: 'test' ...

Subtest: duplicate trackers and hints stay in sync

ok 5 - duplicate trackers and hints stay in sync

duration_ms: 0.887102 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.441196 type: 'test' ...

Subtest: object helper allows HTTP seeds on HTTP origins

ok 7 - object helper allows HTTP seeds on HTTP origins

duration_ms: 0.437018 type: 'test' ...

Subtest: extractMagnetHints returns first ws/xs pair

ok 8 - extractMagnetHints returns first ws/xs pair

duration_ms: 0.485335 type: 'test' ...

Subtest: object helper reports didChange when output differs

ok 9 - object helper reports didChange when output differs

duration_ms: 0.799099 type: 'test' ...

Subtest: helpers trim fragments from non-magnet values

ok 10 - helpers trim fragments from non-magnet values

duration_ms: 0.541386 type: 'test' ...

Subtest: normalizeAndAugmentMagnet filters out known broken trackers

ok 11 - normalizeAndAugmentMagnet filters out known broken trackers

duration_ms: 0.699324 type: 'test' ...

Subtest: normalizeMagnetInput parses each inbound parameter exactly once

ok 12 - normalizeMagnetInput parses each inbound parameter exactly once

duration_ms: 0.343789 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.552839 type: 'test' ...

Subtest: normalizeMagnetInput preserves parameter values containing additional equals signs

ok 14 - normalizeMagnetInput preserves parameter values containing additional equals signs

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

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.739641

→ 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: 92.414421 type: 'test' ...

Subtest: MediaLoader loads video sources and poster fallbacks

ok 2 - MediaLoader loads video sources and poster fallbacks

duration_ms: 20.442901 type: 'test' ...

Subtest: MediaLoader clears unsupported lazy targets without inline styles

ok 3 - MediaLoader clears unsupported lazy targets without inline styles

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 136.741947

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

Subtest: can import channelProfile

ok 1 - can import channelProfile

duration_ms: 0.994834 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 25.897195

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

Subtest: can import WebTorrent

ok 1 - can import WebTorrent

duration_ms: 1.157708 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 11.806098

→ 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: 249.006191 type: 'test' ...

Subtest: UploadModal backdrop click closes and restores trigger focus

ok 2 - UploadModal backdrop click closes and restores trigger focus

duration_ms: 97.051485 type: 'test' ...

Subtest: UploadModal mode toggle updates button states

ok 3 - UploadModal mode toggle updates button states

duration_ms: 70.782592 type: 'test' ...

Subtest: EditModal Escape closes and restores trigger focus

ok 4 - EditModal Escape closes and restores trigger focus

duration_ms: 106.281764 type: 'test' ...

Subtest: EditModal backdrop click closes and restores trigger focus

ok 5 - EditModal backdrop click closes and restores trigger focus

duration_ms: 84.293915 type: 'test' ...

Subtest: EditModal visibility toggle updates button state

ok 6 - EditModal visibility toggle updates button state

duration_ms: 79.462354 type: 'test' ...

Subtest: RevertModal Escape closes and restores trigger focus

ok 7 - RevertModal Escape closes and restores trigger focus

duration_ms: 56.359713 type: 'test' ...

Subtest: static modal helper toggles accessibility hooks

ok 8 - static modal helper toggles accessibility hooks

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

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 768.12839

→ 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.027012 type: 'test' ...

Subtest: user block updates recompute summaries and emit notifications

ok 2 - user block updates recompute summaries and emit notifications

duration_ms: 4.82425 type: 'test' ...

Subtest: moderation thresholds emit logger hooks only when crossing

ok 3 - moderation thresholds emit logger hooks only when crossing

duration_ms: 1.700915 type: 'test' ...

Subtest: trusted mute aggregation tracks F1 mute lists

ok 4 - trusted mute aggregation tracks F1 mute lists

duration_ms: 1.819124 type: 'test' ...

Subtest: viewer mute list publishes and updates aggregation

ok 5 - viewer mute list publishes and updates aggregation

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2023.754616

→ 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: 4.43076 type: 'test' ...

Subtest: moderation stage annotates trusted mute metadata

ok 2 - moderation stage annotates trusted mute metadata

duration_ms: 1.032732 type: 'test' ...

Subtest: moderation stage applies provided thresholds

ok 3 - moderation stage applies provided thresholds

duration_ms: 1.143524 type: 'test' ...

Subtest: moderation stage respects runtime threshold changes

ok 4 - moderation stage respects runtime threshold changes

duration_ms: 0.841639 type: 'test' ...

Subtest: moderation stage supports function-based threshold resolvers

ok 5 - moderation stage supports function-based threshold resolvers

duration_ms: 0.841509 type: 'test' ...

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

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

duration_ms: 2.741198 type: 'test' ...

Subtest: moderation stage blurs viewer-muted authors

ok 7 - moderation stage blurs viewer-muted authors

duration_ms: 0.740152 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.936532 type: 'test' ... 1..8

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.889741

→ 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.307914 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.768113 type: 'test' ...

Subtest: moderation stage respects runtime hide threshold changes

ok 3 - moderation stage respects runtime hide threshold changes

duration_ms: 1.330567 type: 'test' ...

Subtest: moderation stage supports trusted mute hide resolver functions

ok 4 - moderation stage supports trusted mute hide resolver functions

duration_ms: 1.243554 type: 'test' ...

Subtest: moderation stage bypasses hard hides on home feed

ok 5 - moderation stage bypasses hard hides on home feed

duration_ms: 0.693121 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.854737 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.055617

→ 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.333287 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.703444

→ 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.127724 type: 'test' ...

Subtest: trusted seeds contribute to trusted mute counts

ok 2 - trusted seeds contribute to trusted mute counts

duration_ms: 4.232657 type: 'test' ...

Subtest: trusted seeds contribute to trusted report counts

ok 3 - trusted seeds contribute to trusted report counts

duration_ms: 3.634997 type: 'test' ...

Subtest: trusted seed updates recompute guest report summaries

ok 4 - trusted seed updates recompute guest report summaries

duration_ms: 1.270407 type: 'test' ...

Subtest: moderator seeds populate trusted contacts

ok 5 - moderator seeds populate trusted contacts

duration_ms: 0.895566 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: 87.963663 type: 'test' ... 1..6

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3607.659771

→ 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: 14.515841 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2016.203764

→ 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.918011 type: 'test' ...

Subtest: trustedReportCount ignores reports from muted or blocked reporters

ok 2 - trustedReportCount ignores reports from muted or blocked reporters

duration_ms: 2.381756 type: 'test' ...

Subtest: blocking and unblocking reporters recomputes trusted summaries

ok 3 - blocking and unblocking reporters recomputes trusted summaries

duration_ms: 3.095658 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.187196 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2018.748244

→ 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: 144.681435 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: 40.835236 type: 'test' ...

Subtest: VideoCard blurs viewer-muted creators

ok 3 - VideoCard blurs viewer-muted creators

duration_ms: 20.732254 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.904265 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: 37.685888 type: 'test' ...

Subtest: applyModerationContextDatasets clears blur when overrides are active

ok 6 - applyModerationContextDatasets clears blur when overrides are active

duration_ms: 9.669742 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.286441 type: 'test' ... 1..7

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 289.911111

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

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

duration_ms: 3.18697 type: 'test' ...

Subtest: blacklist-author requires moderator access and refreshes subscriptions

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

duration_ms: 0.658543 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.437953 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.668725 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.765252 type: 'test' ...

Subtest: unmute-author removes creators from viewer mute list

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

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.740473

→ 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.800705
  type: 'test'
  ...
# Subtest: respects priority
ok 2 - respects priority
  ---
  duration_ms: 153.461617
  type: 'test'
  ...
1..2

ok 1 - NIP-07 Concurrency Queue

duration_ms: 460.401341 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 468.580986

→ 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: 6.762426 type: 'test' ...

Subtest: imeta tags lower-case mime values

ok 2 - imeta tags lower-case mime values

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.319966

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

Subtest: buildNip71VideoEvent assembles rich metadata

ok 1 - buildNip71VideoEvent assembles rich metadata

duration_ms: 3.908556 type: 'test' ...

Subtest: buildNip71VideoEvent falls back to summary and selects kind

ok 2 - buildNip71VideoEvent falls back to summary and selects kind

duration_ms: 0.388475 type: 'test' ...

Subtest: buildNip71VideoEvent attaches pointer tags

ok 3 - buildNip71VideoEvent attaches pointer tags

duration_ms: 0.427788 type: 'test' ...

Subtest: extractNip71MetadataFromTags parses metadata and pointers

ok 4 - extractNip71MetadataFromTags parses metadata and pointers

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.814664

→ 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: 121.782576 type: 'test' ...

Subtest: hydrateSection renders sanitized hashtags with prefix

ok 2 - hydrateSection renders sanitized hashtags with prefix

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 158.935263

→ 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.783461
  type: 'test'
  ...
# Subtest: NIP-07 decrypt reuses cached extension permissions
ok 2 - NIP-07 decrypt reuses cached extension permissions
  ---
  duration_ms: 1.669692
  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.290436
  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.399236
  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.417568
  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: 301.407257
  type: 'test'
  ...
# Subtest: NIP-07 login surfaces enable permission errors
ok 7 - NIP-07 login surfaces enable permission errors
  ---
  duration_ms: 1.664957
  type: 'test'
  ...
# Subtest: runNip07WithRetry respects timeout without retry multiplier
ok 8 - runNip07WithRetry respects timeout without retry multiplier
  ---
  duration_ms: 61.060977
  type: 'test'
  ...
1..8

ok 1 - NIP-07 Login Permissions

duration_ms: 458.98324 type: 'suite' ... 1..1

tests 8

suites 1

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 467.091321

→ 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.72635 type: 'test' ...

Subtest: Nip46RequestQueue: respects priority levels

ok 2 - Nip46RequestQueue: respects priority levels

duration_ms: 11.318156 type: 'test' ...

Subtest: Nip46RequestQueue: respects minDelayMs

ok 3 - Nip46RequestQueue: respects minDelayMs

duration_ms: 101.625723 type: 'test' ...

Subtest: Nip46RequestQueue: clear() rejects pending tasks

ok 4 - Nip46RequestQueue: clear() rejects pending tasks

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 224.668667

→ 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: 159.867293
  type: 'test'
  ...
1..1

ok 1 - Nostr Private Key Signer

duration_ms: 161.789382 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: 237.792246 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.950266 type: 'test' ...

Subtest: shouldIncludeVideo rejects blacklisted authors provided as npub

ok 2 - shouldIncludeVideo rejects blacklisted authors provided as npub

duration_ms: 2.494208 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: 59.16022 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.430447 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.392154 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: 1.989431 type: 'test' ...

Subtest: generateRandomBytes returns correct length

ok 2 - generateRandomBytes returns correct length

duration_ms: 1.727845 type: 'test' ...

Subtest: encryptSessionPrivateKey and decryptSessionPrivateKey roundtrip

ok 3 - encryptSessionPrivateKey and decryptSessionPrivateKey roundtrip

duration_ms: 358.938055 type: 'test' ...

Subtest: decryptSessionPrivateKey fails with wrong passphrase

ok 4 - decryptSessionPrivateKey fails with wrong passphrase

duration_ms: 305.831394 type: 'test' ...

Subtest: persistSessionActor writes to localStorage and IndexedDB

ok 5 - persistSessionActor writes to localStorage and IndexedDB

duration_ms: 154.092591 type: 'test' ...

Subtest: readStoredSessionActorEntry retrieves from localStorage

ok 6 - readStoredSessionActorEntry retrieves from localStorage

duration_ms: 155.670651 type: 'test' ...

Subtest: clearStoredSessionActor removes from localStorage

ok 7 - clearStoredSessionActor removes from localStorage

duration_ms: 0.840507 type: 'test' ...

Subtest: helper: arrayBufferToBase64 and base64ToUint8Array roundtrip

ok 8 - helper: arrayBufferToBase64 and base64ToUint8Array roundtrip

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

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 990.731496

→ 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.401156 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 112.446898

→ Running tests/nostr-specs/kind30078.test.mjs TAP version 13

Subtest: Kind 30078 (Video Note) Compliance

# Subtest: should have correct kind 30078
ok 1 - should have correct kind 30078
  ---
  duration_ms: 1.459578
  type: 'test'
  ...
# Subtest: should require version 3
ok 2 - should require version 3
  ---
  duration_ms: 0.474092
  type: 'test'
  ...
# Subtest: should build a valid event with required fields
ok 3 - should build a valid event with required fields
  ---
  duration_ms: 1.50592
  type: 'test'
  ...
# Subtest: should allow magnet links
ok 4 - should allow magnet links
  ---
  duration_ms: 0.412031
  type: 'test'
  ...
# Subtest: should include "s" tag for storage pointer
ok 5 - should include "s" tag for storage pointer
  ---
  duration_ms: 0.745555
  type: 'test'
  ...
# Subtest: should include "d" tag
ok 6 - should include "d" tag
  ---
  duration_ms: 0.398488
  type: 'test'
  ...
1..6

ok 1 - Kind 30078 (Video Note) Compliance

duration_ms: 6.705291 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.679748

→ Running tests/nostr-specs/nip04-nip44.test.mjs TAP version 13

Subtest: NIP-04 / NIP-44 Compliance (dmDecryptWorker)

# Subtest: should decrypt NIP-04 messages
ok 1 - should decrypt NIP-04 messages
  ---
  duration_ms: 18.888402
  type: 'test'
  ...
# Subtest: should decrypt NIP-44 v2 messages
ok 2 - should decrypt NIP-44 v2 messages
  ---
  duration_ms: 36.667164
  type: 'test'
  ...
# Subtest: should fail NIP-44 decryption with invalid signature
ok 3 - should fail NIP-44 decryption with invalid signature
  ---
  duration_ms: 8.430144
  type: 'test'
  ...
# Subtest: should prioritize NIP-44 v2
ok 4 - should prioritize NIP-44 v2
  ---
  duration_ms: 0.32993
  type: 'test'
  ...
1..4

ok 1 - NIP-04 / NIP-44 Compliance (dmDecryptWorker)

duration_ms: 130.326712 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 145.031282

→ 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: 64.866283 type: 'test' ...

Subtest: createNip07Adapter maps extension methods and permissions

ok 2 - createNip07Adapter maps extension methods and permissions

duration_ms: 3.105123 type: 'test' ...

Subtest: createNip46Adapter wraps the remote signer client

ok 3 - createNip46Adapter wraps the remote signer client

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 81.277632

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

Subtest: CACHE_POLICIES structure

ok 1 - CACHE_POLICIES structure

duration_ms: 1.448489 type: 'test' ...

Subtest: VIDEO_POST policy

ok 2 - VIDEO_POST policy

duration_ms: 0.256522 type: 'test' ...

Subtest: WATCH_HISTORY policy

ok 3 - WATCH_HISTORY policy

duration_ms: 0.166899 type: 'test' ...

Subtest: SUBSCRIPTION_LIST policy

ok 4 - SUBSCRIPTION_LIST policy

duration_ms: 0.203661 type: 'test' ...

Subtest: VIDEO_COMMENT policy

ok 5 - VIDEO_COMMENT policy

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.379468

→ 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.46491
      type: 'test'
      ...
    1..1
ok 1 - Initialization
  ---
  duration_ms: 4.603492
  type: 'suite'
  ...
# Subtest: fetchListIncrementally
    # Subtest: should fetch events and deduplicate results
    ok 1 - should fetch events and deduplicate results
      ---
      duration_ms: 3.378386
      type: 'test'
      ...
    # Subtest: should handle incremental updates using lastSeen
    ok 2 - should handle incremental updates using lastSeen
      ---
      duration_ms: 1.416573
      type: 'test'
      ...
    # Subtest: should override lastSeen with explicit since parameter
    ok 3 - should override lastSeen with explicit since parameter
      ---
      duration_ms: 1.123345
      type: 'test'
      ...
    # Subtest: should force full fetch if since is 0
    ok 4 - should force full fetch if since is 0
      ---
      duration_ms: 0.918574
      type: 'test'
      ...
    1..4
ok 2 - fetchListIncrementally
  ---
  duration_ms: 7.381119
  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.87777
      type: 'test'
      ...
    1..1
ok 3 - subscribeVideos
  ---
  duration_ms: 302.715334
  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: 5.866044
      type: 'test'
      ...
    # Subtest: should respect since parameter if provided
    ok 2 - should respect since parameter if provided
      ---
      duration_ms: 2.404858
      type: 'test'
      ...
    1..2
ok 4 - fetchVideos
  ---
  duration_ms: 8.725233
  type: 'suite'
  ...
# Subtest: publishVideo
    # Subtest: should throw if not logged in
    ok 1 - should throw if not logged in
      ---
      duration_ms: 2.92436
      type: 'test'
      ...
    # Subtest: should sign and publish a valid video
    ok 2 - should sign and publish a valid video
      ---
      duration_ms: 4.307504
      type: 'test'
      ...
    1..2
ok 5 - publishVideo
  ---
  duration_ms: 7.44766
  type: 'suite'
  ...
# Subtest: editVideo
    # Subtest: should throw if not owner
    ok 1 - should throw if not owner
      ---
      duration_ms: 1.391308
      type: 'test'
      ...
    1..1
ok 6 - editVideo
  ---
  duration_ms: 1.54642
  type: 'suite'
  ...
# Subtest: revertVideo
    # Subtest: should publish a deletion marker event
    ok 1 - should publish a deletion marker event
      ---
      duration_ms: 0.735792
      type: 'test'
      ...
    1..1
ok 7 - revertVideo
  ---
  duration_ms: 0.882681
  type: 'suite'
  ...
1..7

ok 1 - NostrClient

duration_ms: 334.72525 type: 'suite' ... 1..1

tests 12

suites 8

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 552.509092

→ 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: 8.128259 type: 'test' ...

Subtest: publishComment rejects when active signer is unavailable

ok 2 - publishComment rejects when active signer is unavailable

duration_ms: 0.435366 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.454512 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.323216 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.468075 type: 'test' ...

Subtest: listVideoComments matches uppercase definition addresses without lowering

ok 6 - listVideoComments matches uppercase definition addresses without lowering

duration_ms: 0.958382 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: 0.94956 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.327359 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.232667 type: 'test' ...

Subtest: listVideoComments supports legacy targets without a definition address

ok 10 - listVideoComments supports legacy targets without a definition address

duration_ms: 1.056243 type: 'test' ...

Subtest: subscribeVideoComments forwards matching events and cleans up unsubscribe

ok 11 - subscribeVideoComments forwards matching events and cleans up unsubscribe

duration_ms: 10.481516 type: 'test' ...

Subtest: subscribeVideoComments supports video targets without a definition address

ok 12 - subscribeVideoComments supports video targets without a definition address

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

tests 12

suites 0

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 40.306158

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

Subtest: CommentTargetNormalizer Utilities

# Subtest: normalizeRelay
ok 1 - normalizeRelay
  ---
  duration_ms: 1.337026
  type: 'test'
  ...
# Subtest: normalizePointerCandidate
ok 2 - normalizePointerCandidate
  ---
  duration_ms: 1.414337
  type: 'test'
  ...
# Subtest: normalizeTagName
ok 3 - normalizeTagName
  ---
  duration_ms: 0.181926
  type: 'test'
  ...
# Subtest: normalizeTagValue
ok 4 - normalizeTagValue
  ---
  duration_ms: 0.248185
  type: 'test'
  ...
# Subtest: normalizeDescriptorString
ok 5 - normalizeDescriptorString
  ---
  duration_ms: 0.310465
  type: 'test'
  ...
# Subtest: pickString
ok 6 - pickString
  ---
  duration_ms: 0.21473
  type: 'test'
  ...
# Subtest: pickKind
ok 7 - pickKind
  ---
  duration_ms: 0.240376
  type: 'test'
  ...
# Subtest: isEventCandidate
ok 8 - isEventCandidate
  ---
  duration_ms: 0.392955
  type: 'test'
  ...
# Subtest: resolveEventCandidate
ok 9 - resolveEventCandidate
  ---
  duration_ms: 0.425447
  type: 'test'
  ...
# Subtest: collectTagsFromEvent
ok 10 - collectTagsFromEvent
  ---
  duration_ms: 0.711033
  type: 'test'
  ...
# Subtest: findTagByName
ok 11 - findTagByName
  ---
  duration_ms: 0.353138
  type: 'test'
  ...
1..11

ok 1 - CommentTargetNormalizer Utilities

duration_ms: 7.806622 type: 'suite' ...

Subtest: CommentTargetNormalizer Class

# Subtest: normalize with full explicit descriptor
ok 1 - normalize with full explicit descriptor
  ---
  duration_ms: 1.438458
  type: 'test'
  ...
# Subtest: normalize extracts from tags if missing in descriptor
ok 2 - normalize extracts from tags if missing in descriptor
  ---
  duration_ms: 0.304906
  type: 'test'
  ...
# Subtest: normalize prioritizes overrides over target
ok 3 - normalize prioritizes overrides over target
  ---
  duration_ms: 0.209124
  type: 'test'
  ...
# Subtest: normalize derives defaults correctly
ok 4 - normalize derives defaults correctly
  ---
  duration_ms: 0.391179
  type: 'test'
  ...
# Subtest: returns null if videoEventId is missing
ok 5 - returns null if videoEventId is missing
  ---
  duration_ms: 0.186418
  type: 'test'
  ...
1..5

ok 2 - CommentTargetNormalizer Class

duration_ms: 2.776216 type: 'suite' ... 1..2

tests 16

suites 2

pass 16

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.427397

→ 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.913498
      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.657086
      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.697396
      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.587216
      type: 'test'
      ...
    1..4
ok 1 - isVerboseDiagnosticsEnabled
  ---
  duration_ms: 5.38685
  type: 'suite'
  ...
# Subtest: logRelayCountFailure
    # Subtest: should log warning for new relay URL
    ok 1 - should log warning for new relay URL
      ---
      duration_ms: 2.132333
      type: 'test'
      ...
    # Subtest: should suppress 'Failed to connect to relay' errors
    ok 2 - should suppress 'Failed to connect to relay' errors
      ---
      duration_ms: 0.581177
      type: 'test'
      ...
    # Subtest: should throttle duplicate warnings for the same relay
    ok 3 - should throttle duplicate warnings for the same relay
      ---
      duration_ms: 0.713873
      type: 'test'
      ...
    # Subtest: should handle empty or non-string relay URLs
    ok 4 - should handle empty or non-string relay URLs
      ---
      duration_ms: 0.92728
      type: 'test'
      ...
    1..4
ok 2 - logRelayCountFailure
  ---
  duration_ms: 4.926077
  type: 'suite'
  ...
# Subtest: Other Loggers
    # Subtest: should log timeout cleanup failure once
    ok 1 - should log timeout cleanup failure once
      ---
      duration_ms: 0.991721
      type: 'test'
      ...
    # Subtest: should log rebroadcast failure once
    ok 2 - should log rebroadcast failure once
      ---
      duration_ms: 0.577738
      type: 'test'
      ...
    # Subtest: should log view count failure once
    ok 3 - should log view count failure once
      ---
      duration_ms: 0.678467
      type: 'test'
      ...
    1..3
ok 3 - Other Loggers
  ---
  duration_ms: 2.62713
  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.756477
      type: 'test'
      ...
    # Subtest: should not consume throttle key when disabled
    ok 2 - should not consume throttle key when disabled
      ---
      duration_ms: 0.733307
      type: 'test'
      ...
    1..2
ok 4 - Verbose Mode Disabled
  ---
  duration_ms: 1.779157
  type: 'suite'
  ...
1..4

ok 1 - countDiagnostics

duration_ms: 21.077377 type: 'suite' ... 1..1

tests 13

suites 5

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 55.942529

→ 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: 4.316529
  type: 'test'
  ...
# Subtest: returns null for unknown pubkey
ok 2 - returns null for unknown pubkey
  ---
  duration_ms: 1.377309
  type: 'test'
  ...
# Subtest: handles invalid inputs gracefully
ok 3 - handles invalid inputs gracefully
  ---
  duration_ms: 1.891429
  type: 'test'
  ...
# Subtest: expires entries after TTL (2 hours)
ok 4 - expires entries after TTL (2 hours)
  ---
  duration_ms: 2.722384
  type: 'test'
  ...
# Subtest: does not expire entries before TTL
ok 5 - does not expire entries before TTL
  ---
  duration_ms: 0.6687
  type: 'test'
  ...
# Subtest: clears cache
ok 6 - clears cache
  ---
  duration_ms: 1.271193
  type: 'test'
  ...
1..6

ok 1 - decryptionSchemeCache

duration_ms: 21.746616 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 53.338701

→ 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: 2.014825
  type: 'test'
  ...
# Subtest: should be configured with default relays
ok 2 - should be configured with default relays
  ---
  duration_ms: 1.973806
  type: 'test'
  ...
# Subtest: should be initialized with default read relays
ok 3 - should be initialized with default read relays
  ---
  duration_ms: 0.606362
  type: 'test'
  ...
# Subtest: should be initialized with default write relays
ok 4 - should be initialized with default write relays
  ---
  duration_ms: 0.577806
  type: 'test'
  ...
# Subtest: should be registered as the default client
ok 5 - should be registered as the default client
  ---
  duration_ms: 0.455663
  type: 'test'
  ...
1..5

ok 1 - Default NostrClient

duration_ms: 9.21097 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 35.0408

→ 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: 494.520436 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: 235.036849 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: 223.877572 type: 'test' ... 1..3

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 970.73248

→ 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: 6.36412
      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.624725
      type: 'test'
      ...
    # Subtest: should summarize a valid event correctly
    ok 3 - should summarize a valid event correctly
      ---
      duration_ms: 0.904045
      type: 'test'
      ...
    # Subtest: should handle non-finite created_at
    ok 4 - should handle non-finite created_at
      ---
      duration_ms: 0.658213
      type: 'test'
      ...
    # Subtest: should handle non-finite kind
    ok 5 - should handle non-finite kind
      ---
      duration_ms: 0.547145
      type: 'test'
      ...
    # Subtest: should handle missing or invalid content
    ok 6 - should handle missing or invalid content
      ---
      duration_ms: 0.677241
      type: 'test'
      ...
    # Subtest: should handle missing or invalid tags
    ok 7 - should handle missing or invalid tags
      ---
      duration_ms: 1.138139
      type: 'test'
      ...
    1..7
ok 1 - summarizeDmEventForLog
  ---
  duration_ms: 15.425664
  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: 1.499646
      type: 'test'
      ...
    # Subtest: should handle string errors
    ok 2 - should handle string errors
      ---
      duration_ms: 1.079735
      type: 'test'
      ...
    # Subtest: should handle Error objects with standard properties
    ok 3 - should handle Error objects with standard properties
      ---
      duration_ms: 0.854859
      type: 'test'
      ...
    # Subtest: should handle Error objects with missing properties
    ok 4 - should handle Error objects with missing properties
      ---
      duration_ms: 0.517443
      type: 'test'
      ...
    # Subtest: should handle Error objects with non-string properties
    ok 5 - should handle Error objects with non-string properties
      ---
      duration_ms: 0.673064
      type: 'test'
      ...
    1..5
ok 2 - sanitizeDecryptError
  ---
  duration_ms: 6.325413
  type: 'suite'
  ...
1..2

ok 1 - dmDecryptDiagnostics

duration_ms: 24.741899 type: 'suite' ... 1..1

tests 12

suites 3

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 105.131393

→ 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: 8.409148 type: 'test' ...

Subtest: decryptDM falls back to nip04 when nip44 fails

ok 2 - decryptDM falls back to nip04 when nip44 fails

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 41.68282

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

Subtest: dmDecryptWorkerClient

# Subtest: should support worker environment
ok 1 - should support worker environment
  ---
  duration_ms: 8.553729
  type: 'test'
  ...
# Subtest: should reject if Worker is unavailable
ok 2 - should reject if Worker is unavailable
  ---
  duration_ms: 4.704142
  type: 'test'
  ...
# Subtest: should reject if inputs are invalid
ok 3 - should reject if inputs are invalid
  ---
  duration_ms: 4.576915
  type: 'test'
  ...
# Subtest: should successfully decrypt message
ok 4 - should successfully decrypt message
  ---
  duration_ms: 15.369849
  type: 'test'
  ...
# Subtest: should handle worker error response
ok 5 - should handle worker error response
  ---
  duration_ms: 5.489939
  type: 'test'
  ...
# Subtest: should handle worker error event
ok 6 - should handle worker error event
  ---
  duration_ms: 7.286988
  type: 'test'
  ...
# Subtest: should timeout if worker does not respond
ok 7 - should timeout if worker does not respond
  ---
  duration_ms: 103.284217
  type: 'test'
  ...
# Subtest: should initialize worker lazily and reuse instance
ok 8 - should initialize worker lazily and reuse instance
  ---
  duration_ms: 3.860229
  type: 'test'
  ...
# Subtest: should recreate worker if creation fails
ok 9 - should recreate worker if creation fails
  ---
  duration_ms: 2.12305
  type: 'test'
  ...
1..9

ok 1 - dmDecryptWorkerClient

duration_ms: 159.591892 type: 'suite' ... 1..1

tests 9

suites 1

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 177.985908

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

Subtest: dmSignalEvents

# Subtest: publishDmReadReceipt should succeed
ok 1 - publishDmReadReceipt should succeed
  ---
  duration_ms: 13.344236
  type: 'test'
  ...
# Subtest: publishDmReadReceipt should fail if session actor
ok 2 - publishDmReadReceipt should fail if session actor
  ---
  duration_ms: 1.645545
  type: 'test'
  ...
# Subtest: publishDmTypingIndicator should succeed
ok 3 - publishDmTypingIndicator should succeed
  ---
  duration_ms: 17.75856
  type: 'test'
  ...
# Subtest: should fail if no signer
ok 4 - should fail if no signer
  ---
  duration_ms: 2.460367
  type: 'test'
  ...
1..4

ok 1 - dmSignalEvents

duration_ms: 188.197489 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 209.254719

→ 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: 12.952796 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.674621 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.623368

→ 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: 16.873022
  type: 'test'
  ...
# Subtest: persistSnapshot should only update changed items
ok 2 - persistSnapshot should only update changed items
  ---
  duration_ms: 3.994352
  type: 'test'
  ...
# Subtest: persistSnapshot should delete removed items
ok 3 - persistSnapshot should delete removed items
  ---
  duration_ms: 3.20504
  type: 'test'
  ...
# Subtest: persistSnapshot should respect dirty keys optimization
ok 4 - persistSnapshot should respect dirty keys optimization
  ---
  duration_ms: 2.677616
  type: 'test'
  ...
1..4

ok 1 - EventsCacheStore

duration_ms: 28.524578 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 36.674313

→ 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: 1.133733
  type: 'test'
  ...
# Subtest: updates index on delete
ok 2 - updates index on delete
  ---
  duration_ms: 0.374554
  type: 'test'
  ...
# Subtest: updates index on overwrite with different object
ok 3 - updates index on overwrite with different object
  ---
  duration_ms: 0.223557
  type: 'test'
  ...
# Subtest: clears index on clear
ok 4 - clears index on clear
  ---
  duration_ms: 0.315743
  type: 'test'
  ...
# Subtest: normalizes pubkeys
ok 5 - normalizes pubkeys
  ---
  duration_ms: 0.308257
  type: 'test'
  ...
# Subtest: handles non-event objects gracefully
ok 6 - handles non-event objects gracefully
  ---
  duration_ms: 0.354332
  type: 'test'
  ...
1..6

ok 1 - EventsMap functionality

duration_ms: 5.968287 type: 'test' ... 1..1

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.980831

→ 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.996975 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 612.57121

→ 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.81398
      type: 'test'
      ...
    # Subtest: should return array with string for string input
    ok 2 - should return array with string for string input
      ---
      duration_ms: 0.470638
      type: 'test'
      ...
    # Subtest: should extract relevant fields from object
    ok 3 - should extract relevant fields from object
      ---
      duration_ms: 0.670574
      type: 'test'
      ...
    # Subtest: should ignore non-string fields in object
    ok 4 - should ignore non-string fields in object
      ---
      duration_ms: 0.56656
      type: 'test'
      ...
    1..4
ok 1 - collectCandidateStrings
  ---
  duration_ms: 4.577783
  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.703469
      type: 'test'
      ...
    # Subtest: should suppress warning by code string
    ok 2 - should suppress warning by code string
      ---
      duration_ms: 0.244902
      type: 'test'
      ...
    # Subtest: should suppress warning by object code property
    ok 3 - should suppress warning by object code property
      ---
      duration_ms: 0.300025
      type: 'test'
      ...
    # Subtest: should suppress warning by message snippet
    ok 4 - should suppress warning by message snippet
      ---
      duration_ms: 0.373726
      type: 'test'
      ...
    # Subtest: should suppress warning by object message property snippet
    ok 5 - should suppress warning by object message property snippet
      ---
      duration_ms: 0.448829
      type: 'test'
      ...
    # Subtest: should NOT suppress unrelated warnings
    ok 6 - should NOT suppress unrelated warnings
      ---
      duration_ms: 0.357041
      type: 'test'
      ...
    # Subtest: should handle multiple arguments
    ok 7 - should handle multiple arguments
      ---
      duration_ms: 0.445536
      type: 'test'
      ...
    1..7
ok 2 - shouldSuppressWarning
  ---
  duration_ms: 3.54514
  type: 'suite'
  ...
# Subtest: process.emitWarning patch
    # Subtest: should have patched process.emitWarning
    ok 1 - should have patched process.emitWarning
      ---
      duration_ms: 0.237597
      type: 'test'
      ...
    1..1
ok 3 - process.emitWarning patch
  ---
  duration_ms: 0.369062
  type: 'suite'
  ...
1..3

ok 1 - maxListenerDiagnostics

duration_ms: 10.202282 type: 'suite' ... 1..1

tests 12

suites 4

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.043082

→ 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.307494
  type: 'test'
  ...
# Subtest: should reject when worker returns error
ok 2 - should reject when worker returns error
  ---
  duration_ms: 5.201675
  type: 'test'
  ...
# Subtest: should reject when worker emits error event
ok 3 - should reject when worker emits error event
  ---
  duration_ms: 3.100498
  type: 'test'
  ...
# Subtest: should timeout if worker does not respond
ok 4 - should timeout if worker does not respond
  ---
  duration_ms: 103.099817
  type: 'test'
  ...
# Subtest: should reject immediately if inputs are missing
ok 5 - should reject immediately if inputs are missing
  ---
  duration_ms: 2.655783
  type: 'test'
  ...
# Subtest: should reject if Worker API is unavailable
ok 6 - should reject if Worker API is unavailable
  ---
  duration_ms: 2.031687
  type: 'test'
  ...
# Subtest: should handle worker creation failure
ok 7 - should handle worker creation failure
  ---
  duration_ms: 1.951083
  type: 'test'
  ...
1..7

ok 1 - nip04WorkerClient

duration_ms: 131.586972 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 142.880297

→ 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.584083
  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.340989
  type: 'test'
  ...
1..2

ok 1 - Nip07Adapter Race Condition

duration_ms: 6.419856 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.847171

→ 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.92101 type: 'test' ...

Subtest: clearStoredNip07Permissions removes persisted grants

ok 2 - clearStoredNip07Permissions removes persisted grants

duration_ms: 0.374388 type: 'test' ...

Subtest: requestEnablePermissions retries explicit and fallback variants

ok 3 - requestEnablePermissions retries explicit and fallback variants

duration_ms: 1.980234 type: 'test' ...

Subtest: requestEnablePermissions reports unavailable extension

ok 4 - requestEnablePermissions reports unavailable extension

duration_ms: 0.22715 type: 'test' ...

Subtest: waitForNip07Extension resolves when extension is present

ok 5 - waitForNip07Extension resolves when extension is present

duration_ms: 0.626374 type: 'test' ...

Subtest: waitForNip07Extension resolves when extension appears later

ok 6 - waitForNip07Extension resolves when extension appears later

duration_ms: 51.222186 type: 'test' ...

Subtest: waitForNip07Extension rejects when extension never appears

ok 7 - waitForNip07Extension rejects when extension never appears

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

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 216.724012

→ 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: 166.060966 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys handles nip44.v2 ciphertext

ok 2 - decryptNip46PayloadWithKeys handles nip44.v2 ciphertext

duration_ms: 23.077577 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys coerces structured handshake payloads

ok 3 - decryptNip46PayloadWithKeys coerces structured handshake payloads

duration_ms: 23.099949 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys decodes buffer-based handshake payloads

ok 4 - decryptNip46PayloadWithKeys decodes buffer-based handshake payloads

duration_ms: 22.66871 type: 'test' ...

Subtest: decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers

ok 5 - decryptNip46PayloadWithKeys supports nip04-style ciphertext wrappers

duration_ms: 32.188349 type: 'test' ...

Subtest: parseNip46ConnectionString handles remote signer key hints

ok 6 - parseNip46ConnectionString handles remote signer key hints

duration_ms: 5.562552 type: 'test' ...

Subtest: attemptDecryptNip46HandshakePayload falls back to expected remote signer key

ok 7 - attemptDecryptNip46HandshakePayload falls back to expected remote signer key

duration_ms: 41.004476 type: 'test' ...

Subtest: attemptDecryptNip46HandshakePayload handles array-encoded nip04 payloads

ok 8 - attemptDecryptNip46HandshakePayload handles array-encoded nip04 payloads

duration_ms: 27.215356 type: 'test' ...

Subtest: Nip46RpcClient sendRpc publishes events and resolves responses

ok 9 - Nip46RpcClient sendRpc publishes events and resolves responses

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

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 361.789088

→ 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.895437
      type: 'test'
      ...
    # Subtest: should use existing keys if provided
    ok 2 - should use existing keys if provided
      ---
      duration_ms: 0.486583
      type: 'test'
      ...
    1..2
ok 1 - createKeyPair
  ---
  duration_ms: 5.797627
  type: 'suite'
  ...
# Subtest: prepareHandshake
    # Subtest: should parse connection string and return details
    ok 1 - should parse connection string and return details
      ---
      duration_ms: 1.583629
      type: 'test'
      ...
    1..1
ok 2 - prepareHandshake
  ---
  duration_ms: 1.989134
  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.372415
      type: 'test'
      ...
    1..1
ok 3 - connect
  ---
  duration_ms: 2.660871
  type: 'suite'
  ...
# Subtest: reconnectStored
    # Subtest: should throw if no stored session
    ok 1 - should throw if no stored session
      ---
      duration_ms: 1.93427
      type: 'test'
      ...
    # Subtest: should reconnect using stored session
    ok 2 - should reconnect using stored session
      ---
      duration_ms: 1.06716
      type: 'test'
      ...
    1..2
ok 4 - reconnectStored
  ---
  duration_ms: 3.56401
  type: 'suite'
  ...
# Subtest: disconnect
    # Subtest: should clear stored session and emit change
    ok 1 - should clear stored session and emit change
      ---
      duration_ms: 0.555623
      type: 'test'
      ...
    1..1
ok 5 - disconnect
  ---
  duration_ms: 0.79497
  type: 'suite'
  ...
1..5

ok 1 - Nip46Connector

duration_ms: 16.297866 type: 'suite' ... 1..1

tests 7

suites 6

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.896963

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

Subtest: buildNip71MetadataTags normalizes structured fields

ok 1 - buildNip71MetadataTags normalizes structured fields

duration_ms: 3.182533 type: 'test' ...

Subtest: collectNip71PointerRequests aggregates events and tags

ok 2 - collectNip71PointerRequests aggregates events and tags

duration_ms: 0.531836 type: 'test' ...

Subtest: processNip71Events reconciles pointers and filters video hashtags

ok 3 - processNip71Events reconciles pointers and filters video hashtags

duration_ms: 1.309591 type: 'test' ...

Subtest: populateNip71MetadataForVideos fetches missing records once

ok 4 - populateNip71MetadataForVideos fetches missing records once

duration_ms: 0.977327 type: 'test' ...

Subtest: buildNip71VideoEvent composes pointer tags

ok 5 - buildNip71VideoEvent composes pointer tags

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.702131

→ 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.108003 type: 'test' ...

Subtest: nostrClientFacade forwards the default permission helper

ok 2 - nostrClientFacade forwards the default permission helper

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12.298025

→ 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.386663 type: 'test' ...

Subtest: setActiveSigner notifies listeners and resolves by pubkey

ok 2 - setActiveSigner notifies listeners and resolves by pubkey

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.744291

→ 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.561846 type: 'test' ...

Subtest: mirrorVideoEvent lowercases inferred MIME types

ok 2 - mirrorVideoEvent lowercases inferred MIME types

duration_ms: 0.773218 type: 'test' ...

Subtest: mirrorVideoEvent includes hash tags when provided

ok 3 - mirrorVideoEvent includes hash tags when provided

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.669669

→ 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.48063 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.513508 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.623622

→ 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: 6.964853 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.236931 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.039169

→ 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: 299.337513
  type: 'test'
  ...
# Subtest: fails to decrypt with wrong passphrase
ok 2 - fails to decrypt with wrong passphrase
  ---
  duration_ms: 293.714479
  type: 'test'
  ...
# Subtest: persists and reads session actor
ok 3 - persists and reads session actor
  ---
  duration_ms: 2.364754
  type: 'test'
  ...
# Subtest: clears stored session actor
ok 4 - clears stored session actor
  ---
  duration_ms: 0.554296
  type: 'test'
  ...
1..4

ok 1 - SessionActor

duration_ms: 598.499669 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 618.213666

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

Subtest: encryptSessionPrivateKey + decryptSessionPrivateKey roundtrip

ok 1 - encryptSessionPrivateKey + decryptSessionPrivateKey roundtrip

duration_ms: 299.451507 type: 'test' ...

Subtest: persistSessionActor stores encrypted payload metadata

ok 2 - persistSessionActor stores encrypted payload metadata

duration_ms: 147.629349 type: 'test' ...

Subtest: clearStoredSessionActor removes persisted payload

ok 3 - clearStoredSessionActor removes persisted payload

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 603.705587

→ 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: 300.184752
  type: 'test'
  ...
# Subtest: Decryption fails with wrong passphrase
ok 2 - Decryption fails with wrong passphrase
  ---
  duration_ms: 294.062346
  type: 'test'
  ...
# Subtest: Persistence and Retrieval
ok 3 - Persistence and Retrieval
  ---
  duration_ms: 299.623651
  type: 'test'
  ...
# Subtest: Clear Stored Session Actor
ok 4 - Clear Stored Session Actor
  ---
  duration_ms: 0.863823
  type: 'test'
  ...
1..4

ok 1 - js/nostr/sessionActor.js

duration_ms: 897.937828 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 907.653638

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

Subtest: queueSignEvent: processes requests sequentially

ok 1 - queueSignEvent: processes requests sequentially

duration_ms: 104.588456 type: 'test' ...

Subtest: queueSignEvent: handles timeouts

ok 2 - queueSignEvent: handles timeouts

duration_ms: 21.616786 type: 'test' ...

Subtest: queueSignEvent: handles permission denied errors

ok 3 - queueSignEvent: handles permission denied errors

duration_ms: 0.589742 type: 'test' ...

Subtest: queueSignEvent: handles signer disconnected

ok 4 - queueSignEvent: handles signer disconnected

duration_ms: 0.429438 type: 'test' ...

Subtest: queueSignEvent: fails if signer is missing or invalid

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

duration_ms: 0.507744 type: 'test' ...

Subtest: queueSignEvent: continues queue processing after failure

ok 6 - queueSignEvent: continues queue processing after failure

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 214.208955

→ 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: 1.761232 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12.111455

→ 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: 2.011306
  type: 'test'
  ...
# Subtest: should update last seen and save to localStorage
ok 2 - should update last seen and save to localStorage
  ---
  duration_ms: 0.740934
  type: 'test'
  ...
# Subtest: should only update if timestamp is newer
ok 3 - should only update if timestamp is newer
  ---
  duration_ms: 0.909781
  type: 'test'
  ...
# Subtest: should get per-relay last seen
ok 4 - should get per-relay last seen
  ---
  duration_ms: 2.040669
  type: 'test'
  ...
# Subtest: should handle missing localStorage gracefully
ok 5 - should handle missing localStorage gracefully
  ---
  duration_ms: 0.399383
  type: 'test'
  ...
1..5

ok 1 - SyncMetadataStore

duration_ms: 8.305006 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 27.096892

→ 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.647268 type: 'test' ...

Subtest: toolkit: resolveSimplePoolConstructor finds SimplePool

ok 2 - toolkit: resolveSimplePoolConstructor finds SimplePool

duration_ms: 0.67907 type: 'test' ...

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

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

duration_ms: 0.826898 type: 'test' ...

Subtest: toolkit: readToolkitFromScope finds NostrTools in global scope

ok 4 - toolkit: readToolkitFromScope finds NostrTools in global scope

duration_ms: 0.406483 type: 'test' ...

Subtest: toolkit: normalizeToolkitCandidate validation

ok 5 - toolkit: normalizeToolkitCandidate validation

duration_ms: 0.743602 type: 'test' ...

Subtest: toolkit: shimLegacySimplePoolMethods handles simple list operation

ok 6 - toolkit: shimLegacySimplePoolMethods handles simple list operation

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 26.23931

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

Subtest: VideoEventBuffer

# Subtest: Push and Flush
ok 1 - Push and Flush
  ---
  duration_ms: 3.200602
  type: 'test'
  ...
# Subtest: Latest Wins
ok 2 - Latest Wins
  ---
  duration_ms: 0.843989
  type: 'test'
  ...
# Subtest: Tombstone Handling
ok 3 - Tombstone Handling
  ---
  duration_ms: 0.930936
  type: 'test'
  ...
# Subtest: Cleanup
ok 4 - Cleanup
  ---
  duration_ms: 0.666895
  type: 'test'
  ...
# Subtest: Visibility Gating
ok 5 - Visibility Gating
  ---
  duration_ms: 0.741756
  type: 'test'
  ...
1..5

ok 1 - VideoEventBuffer

duration_ms: 8.258098 type: 'test' ... 1..1

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 21.728042

→ 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: 1.977334
      type: 'test'
      ...
    # Subtest: handles legacyFormData structure
    ok 2 - handles legacyFormData structure
      ---
      duration_ms: 0.206244
      type: 'test'
      ...
    # Subtest: normalizes boolean flags
    ok 3 - normalizes boolean flags
      ---
      duration_ms: 0.197105
      type: 'test'
      ...
    # Subtest: normalizes boolean flags (kids only)
    ok 4 - normalizes boolean flags (kids only)
      ---
      duration_ms: 0.256959
      type: 'test'
      ...
    1..4
ok 1 - extractVideoPublishPayload
  ---
  duration_ms: 3.981553
  type: 'suite'
  ...
# Subtest: prepareVideoPublishPayload
    # Subtest: throws if pubkey is missing
    ok 1 - throws if pubkey is missing
      ---
      duration_ms: 1.260946
      type: 'test'
      ...
    # Subtest: generates a valid event structure
    ok 2 - generates a valid event structure
      ---
      duration_ms: 2.63795
      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.461025
      type: 'test'
      ...
    # Subtest: uses provided seriesIdentifier as d tag
    ok 4 - uses provided seriesIdentifier as d tag
      ---
      duration_ms: 0.553782
      type: 'test'
      ...
    # Subtest: resolves infoHash from magnet
    ok 5 - resolves infoHash from magnet
      ---
      duration_ms: 0.700694
      type: 'test'
      ...
    # Subtest: includes NIP-71 tags
    ok 6 - includes NIP-71 tags
      ---
      duration_ms: 1.398152
      type: 'test'
      ...
    1..6
ok 2 - prepareVideoPublishPayload
  ---
  duration_ms: 7.728772
  type: 'suite'
  ...
1..2

ok 1 - videoPayloadBuilder

duration_ms: 12.495303 type: 'suite' ... 1..1

tests 10

suites 3

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.406875

→ 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: 3.3149 type: 'test' ...

Subtest: getWatchHistoryStorage prunes entries that exceed the configured TTL

ok 2 - getWatchHistoryStorage prunes entries that exceed the configured TTL

duration_ms: 3.264071 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: 10.591071 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: 2.73713 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: 5.220067 type: 'test' ...

Subtest: publishWatchHistorySnapshot caches successful snapshot results

ok 6 - publishWatchHistorySnapshot caches successful snapshot results

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.093032

→ 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: 2.285423 type: 'test' ...

Subtest: removeWatchHistoryItem delegates to the client's manager

ok 2 - removeWatchHistoryItem delegates to the client's manager

duration_ms: 0.488441 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: 0.789109 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.427285 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.115585

→ 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: 2.184668 type: 'test' ...

Subtest: updateActiveNwcSettings returns cloned values

ok 2 - updateActiveNwcSettings returns cloned values

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 13.642281

→ 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: 6.384345 type: 'test' ...

Subtest: resolveVideoPostedAtBatch uses batch hydration

ok 2 - resolveVideoPostedAtBatch uses batch hydration

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.23793

→ 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: 390.545016 type: 'test' ...

Subtest: closes previously open popovers without restoring focus

ok 2 - closes previously open popovers without restoring focus

duration_ms: 68.486933 type: 'test' ...

Subtest: restores focus to the trigger when closed

ok 3 - restores focus to the trigger when closed

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 513.786491

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

Subtest: ProfileCache: setActiveProfile and getActiveProfile

ok 1 - ProfileCache: setActiveProfile and getActiveProfile

duration_ms: 4.67612 type: 'test' ...

Subtest: ProfileCache: resolveAddressKey

ok 2 - ProfileCache: resolveAddressKey

duration_ms: 1.15073 type: 'test' ...

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

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

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

Subtest: ProfileCache: setProfile normalization and storage

ok 4 - ProfileCache: setProfile normalization and storage

duration_ms: 24.702304 type: 'test' ...

Subtest: ProfileCache: TTL expiration

ok 5 - ProfileCache: TTL expiration

duration_ms: 3.955173 type: 'test' ...

Subtest: ProfileCache: clearMemoryCache and clearSignerRuntime

ok 6 - ProfileCache: clearMemoryCache and clearSignerRuntime

duration_ms: 1.081753 type: 'test' ...

Subtest: ProfileCache: listeners

ok 7 - ProfileCache: listeners

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

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 81.671672

→ 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: 4962.186157 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: 4205.326251 type: 'test' ...

Subtest: Profile modal navigation buttons toggle active state

ok 3 - Profile modal navigation buttons toggle active state

duration_ms: 4676.830178 type: 'test' ...

Subtest: Profile modal toggles mobile menu and pane views

ok 4 - Profile modal toggles mobile menu and pane views

duration_ms: 4732.422016 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: 3487.610296 type: 'test' ...

Subtest: Profile modal uses abbreviated npub display

ok 6 - Profile modal uses abbreviated npub display

duration_ms: 2580.717177 type: 'test' ...

Subtest: renderSavedProfiles applies provider metadata

ok 7 - renderSavedProfiles applies provider metadata

duration_ms: 2426.870277 type: 'test' ...

Subtest: Hashtag pane shows empty states by default

ok 8 - Hashtag pane shows empty states by default

duration_ms: 3011.253413 type: 'test' ...

Subtest: Hashtag pane adds, moves, and removes tags

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

duration_ms: 3300.496017 type: 'test' ...

Subtest: handleAddHashtagPreference publishes updates

ok 10 - handleAddHashtagPreference publishes updates

duration_ms: 3052.90333 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: 3698.627916 type: 'test' ...

Subtest: load() injects markup and caches expected elements

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

duration_ms: 1530.588967 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: 2956.720308 type: 'test' ...

Subtest: populateProfileRelays renders entries and wires action buttons

ok 14 - populateProfileRelays renders entries and wires action buttons

duration_ms: 1562.120925 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: 1524.145487 type: 'test' ...

Subtest: history pane lazily initializes the watch history renderer

ok 16 - history pane lazily initializes the watch history renderer

duration_ms: 4618.690891 type: 'test' ...

Subtest: handleDirectMessagesRelayWarning throttles status updates

ok 17 - handleDirectMessagesRelayWarning throttles status updates

duration_ms: 1586.425814 type: 'test' ...

Subtest: handleDirectMessagesRelayWarning suppresses status updates when disabled

ok 18 - handleDirectMessagesRelayWarning suppresses status updates when disabled

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

tests 18

suites 0

pass 18

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 55492.131911

→ Running tests/race/nostr-client-init-race.test.mjs TAP version 13 [bitvid] [nostr] No relays connected during init. Retrying in the background.

Subtest: NostrClient initialization race condition: concurrent init() calls

ok 1 - NostrClient initialization race condition: concurrent init() calls

duration_ms: 54.956657 type: 'test' ... [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.damus.io', backoffMs: 1000, failureCount: 1, retryAt: 1771245594622, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://nos.lol', backoffMs: 1000, failureCount: 1, retryAt: 1771245594623, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.snort.social', backoffMs: 1000, failureCount: 1, retryAt: 1771245594623, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.primal.net', backoffMs: 1000, failureCount: 1, retryAt: 1771245594623, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.damus.io', backoffMs: 1000, failureCount: 1, retryAt: 1771245637669, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://nos.lol', backoffMs: 1000, failureCount: 1, retryAt: 1771245637669, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.snort.social', backoffMs: 1000, failureCount: 1, retryAt: 1771245637670, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.primal.net', backoffMs: 1000, failureCount: 1, retryAt: 1771245637670, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff expired. [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.damus.io', backoffMs: 1000, failureCount: 1, retryAt: 1771245674709, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://nos.lol', backoffMs: 1000, failureCount: 1, retryAt: 1771245674710, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.snort.social', backoffMs: 1000, failureCount: 1, retryAt: 1771245674710, reason: 'connect-timeout' } [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay.primal.net', backoffMs: 1000, failureCount: 1, retryAt: 1771245674710, reason: 'connect-timeout' } 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 87155.572549

→ 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: 3.398684 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.706261 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.620484

→ 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.523299
  type: 'test'
  ...
# Subtest: open() shows error if not logged in
ok 2 - open() shows error if not logged in
  ---
  duration_ms: 0.397342
  type: 'test'
  ...
# Subtest: handleConfirm() calls revertVideo and refreshes
ok 3 - handleConfirm() calls revertVideo and refreshes
  ---
  duration_ms: 0.485411
  type: 'test'
  ...
1..3

ok 1 - RevertModalController

duration_ms: 4.245627 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.87851

→ 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.221496 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.371131 type: 'test' ...

Subtest: safeDecodeURIComponentLoose trims inputs by default

ok 3 - safeDecodeURIComponentLoose trims inputs by default

duration_ms: 0.197331 type: 'test' ...

Subtest: safeDecodeURIComponentLoose preserves whitespace when trim is false

ok 4 - safeDecodeURIComponentLoose preserves whitespace when trim is false

duration_ms: 0.161175 type: 'test' ...

Subtest: safeDecodeURIComponent handles empty strings consistently

ok 5 - safeDecodeURIComponent handles empty strings consistently

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.515176

→ 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.127891
      type: 'test'
      ...
    # Subtest: should prepare upload with encryption
    ok 2 - should prepare upload with encryption
      ---
      duration_ms: 10.897098
      type: 'test'
      ...
    1..2
ok 1 - prepareAttachmentUpload
  ---
  duration_ms: 14.908538
  type: 'suite'
  ...
# Subtest: downloadAttachment
    # Subtest: should download attachment
    ok 1 - should download attachment
      ---
      duration_ms: 1.889442
      type: 'test'
      ...
    # Subtest: should verify hash
    ok 2 - should verify hash
      ---
      duration_ms: 1.204078
      type: 'test'
      ...
    # Subtest: should throw on hash mismatch
    ok 3 - should throw on hash mismatch
      ---
      duration_ms: 1.338051
      type: 'test'
      ...
    1..3
ok 2 - downloadAttachment
  ---
  duration_ms: 5.073713
  type: 'suite'
  ...
# Subtest: caching
    # Subtest: should cache downloaded attachments
    ok 1 - should cache downloaded attachments
      ---
      duration_ms: 1.089945
      type: 'test'
      ...
    # Subtest: should clear cache
    ok 2 - should clear cache
      ---
      duration_ms: 1.326825
      type: 'test'
      ...
    1..2
ok 3 - caching
  ---
  duration_ms: 2.902652
  type: 'suite'
  ...
1..3

ok 1 - attachmentService

duration_ms: 24.767035 type: 'suite' ... 1..1

tests 7

suites 4

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 40.025774

→ 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: 0.925802
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if empty
    ok 2 - returns 'nip07' fallback if empty
      ---
      duration_ms: 1.522662
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if null
    ok 3 - returns 'nip07' fallback if null
      ---
      duration_ms: 0.253576
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if undefined
    ok 4 - returns 'nip07' fallback if undefined
      ---
      duration_ms: 0.406816
      type: 'test'
      ...
    # Subtest: returns 'nip07' fallback if not a string
    ok 5 - returns 'nip07' fallback if not a string
      ---
      duration_ms: 0.164552
      type: 'test'
      ...
    1..5
ok 1 - normalizeProviderId
  ---
  duration_ms: 4.821112
  type: 'test'
  ...
# Subtest: normalizeAuthType
    # Subtest: prioritizes authTypeCandidate
    ok 1 - prioritizes authTypeCandidate
      ---
      duration_ms: 0.415341
      type: 'test'
      ...
    # Subtest: falls back to providerResult.authType
    ok 2 - falls back to providerResult.authType
      ---
      duration_ms: 0.252669
      type: 'test'
      ...
    # Subtest: falls back to providerResult.providerId
    ok 3 - falls back to providerResult.providerId
      ---
      duration_ms: 0.343693
      type: 'test'
      ...
    # Subtest: falls back to providerId
    ok 4 - falls back to providerId
      ---
      duration_ms: 0.248113
      type: 'test'
      ...
    # Subtest: returns 'nip07' if all else fails
    ok 5 - returns 'nip07' if all else fails
      ---
      duration_ms: 0.131272
      type: 'test'
      ...
    # Subtest: trims whitespace from results
    ok 6 - trims whitespace from results
      ---
      duration_ms: 0.119457
      type: 'test'
      ...
    # Subtest: ignores empty strings in candidates
    ok 7 - ignores empty strings in candidates
      ---
      duration_ms: 0.107072
      type: 'test'
      ...
    1..7
ok 2 - normalizeAuthType
  ---
  duration_ms: 3.028341
  type: 'test'
  ...
1..2

ok 1 - authUtils

duration_ms: 8.994531 type: 'test' ... 1..1

tests 15

suites 0

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 27.425076

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

Subtest: DiscussionCountService

# Subtest: initialization sets defaults
ok 1 - initialization sets defaults
  ---
  duration_ms: 1.222937
  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.637602
  type: 'test'
  ...
# Subtest: refreshCounts returns early if dependencies are missing
ok 3 - refreshCounts returns early if dependencies are missing
  ---
  duration_ms: 1.20435
  type: 'test'
  ...
# Subtest: refreshCounts handles happy path (fetches and updates DOM)
ok 4 - refreshCounts handles happy path (fetches and updates DOM)
  ---
  duration_ms: 86.719682
  type: 'test'
  ...
# Subtest: refreshCounts uses cached count if available
ok 5 - refreshCounts uses cached count if available
  ---
  duration_ms: 2.1949
  type: 'test'
  ...
# Subtest: refreshCounts handles API errors gracefully
ok 6 - refreshCounts handles API errors gracefully
  ---
  duration_ms: 4.200505
  type: 'test'
  ...
# Subtest: refreshCounts handles partial results
ok 7 - refreshCounts handles partial results
  ---
  duration_ms: 3.660473
  type: 'test'
  ...
# Subtest: refreshCounts handles unsupported relays (empty perRelay)
ok 8 - refreshCounts handles unsupported relays (empty perRelay)
  ---
  duration_ms: 2.317334
  type: 'test'
  ...
1..8

ok 1 - DiscussionCountService

duration_ms: 190.083101 type: 'suite' ... 1..1

tests 8

suites 1

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 211.091875

→ 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.897174
  type: 'test'
  ...
# Subtest: collectVideoTags works with lightweight object
ok 2 - collectVideoTags works with lightweight object
  ---
  duration_ms: 13.829825
  type: 'test'
  ...
# Subtest: buildVideoAddressPointer works with lightweight object
ok 3 - buildVideoAddressPointer works with lightweight object
  ---
  duration_ms: 0.625159
  type: 'test'
  ...
# Subtest: toLightweightVideo handles invalid input
ok 4 - toLightweightVideo handles invalid input
  ---
  duration_ms: 0.185452
  type: 'test'
  ...
1..4

ok 1 - exploreDataService optimization

duration_ms: 18.247412 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.891588

→ 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.22274
  type: 'test'
  ...
# Subtest: should handle missing loadLatest method gracefully
ok 2 - should handle missing loadLatest method gracefully
  ---
  duration_ms: 0.454315
  type: 'test'
  ...
# Subtest: should handle loadLatest failure gracefully
ok 3 - should handle loadLatest failure gracefully
  ---
  duration_ms: 8.697416
  type: 'test'
  ...
# Subtest: should return counts from worker on success
ok 4 - should return counts from worker on success
  ---
  duration_ms: 2.387459
  type: 'test'
  ...
# Subtest: should handle worker error gracefully
ok 5 - should handle worker error gracefully
  ---
  duration_ms: 1.635658
  type: 'test'
  ...
1..5

ok 1 - exploreDataService - buildWatchHistoryTagCounts

duration_ms: 25.047414 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 33.109676

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

Subtest: LinkPreviewService

# Subtest: initializes IndexedDB
ok 1 - initializes IndexedDB
  ---
  duration_ms: 14.260621
  type: 'test'
  ...
# Subtest: fetches and caches preview
ok 2 - fetches and caches preview
  ---
  duration_ms: 29.21431
  type: 'test'
  ...
# Subtest: returns null on fetch failure
ok 3 - returns null on fetch failure
  ---
  duration_ms: 2.8502
  type: 'test'
  ...
# Subtest: respects TTL
ok 4 - respects TTL
  ---
  duration_ms: 13.923636
  type: 'test'
  ...
# Subtest: deletePreview removes from cache
ok 5 - deletePreview removes from cache
  ---
  duration_ms: 2.401718
  type: 'test'
  ...
1..5

ok 1 - LinkPreviewService

duration_ms: 66.08823 type: 'test' ... 1..1

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 74.837966

→ 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.879378 type: 'test' ...

Subtest: ModerationActionController: blocks user and updates state

ok 2 - ModerationActionController: blocks user and updates state

duration_ms: 0.949436 type: 'test' ...

Subtest: ModerationActionController: prevents blocking self

ok 3 - ModerationActionController: prevents blocking self

duration_ms: 0.349752 type: 'test' ...

Subtest: ModerationActionController: requires login to block

ok 4 - ModerationActionController: requires login to block

duration_ms: 0.416485 type: 'test' ...

Subtest: ModerationActionController: handles hide action

ok 5 - ModerationActionController: handles hide action

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.949565

→ 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.325351
      type: 'test'
      ...
    1..1
ok 1 - loadVideos
  ---
  duration_ms: 4.510835
  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.224238
      type: 'test'
      ...
    1..1
ok 2 - fetchVideosByAuthors
  ---
  duration_ms: 2.515335
  type: 'suite'
  ...
1..2

ok 1 - NostrService

duration_ms: 7.896396 type: 'suite' ... 1..1

tests 2

suites 3

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 23.884288

→ Running tests/services/playbackService_forcedSource.test.mjs (node:3552) 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: 14.001727
  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.818153
  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.321888
  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.968859
  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: 2.38531
  type: 'test'
  ...
1..5

ok 1 - PlaybackService Forced Source Logic

duration_ms: 117.417484 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 131.197636

→ Running tests/services/playbackService_order.test.mjs (node:3559) 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: 8.483673
      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: 1.832074
      type: 'test'
      ...
    # Subtest: urlFirstEnabled=false: Tries Torrent first, succeeds
    ok 3 - urlFirstEnabled=false: Tries Torrent first, succeeds
      ---
      duration_ms: 1.362394
      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: 2.058008
      type: 'test'
      ...
    # Subtest: forcedSource=torrent overrides urlFirstEnabled=true
    ok 5 - forcedSource=torrent overrides urlFirstEnabled=true
      ---
      duration_ms: 0.873759
      type: 'test'
      ...
    1..5
ok 1 - Playback Execution Order
  ---
  duration_ms: 15.631741
  type: 'suite'
  ...
1..1

ok 1 - PlaybackService Ordering

duration_ms: 17.235826 type: 'suite' ... 1..1

tests 5

suites 2

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.628489

→ Running tests/services/playbackService.test.mjs (node:3566) 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.649963
  type: 'test'
  ...
# Subtest: prepareVideoElement respects localStorage and binds listener
ok 2 - prepareVideoElement respects localStorage and binds listener
  ---
  duration_ms: 4.914259
  type: 'test'
  ...
# Subtest: registerUrlPlaybackWatchdogs triggers onFallback on error
ok 3 - registerUrlPlaybackWatchdogs triggers onFallback on error
  ---
  duration_ms: 1.964923
  type: 'test'
  ...
# Subtest: registerUrlPlaybackWatchdogs triggers onSuccess on playing
ok 4 - registerUrlPlaybackWatchdogs triggers onSuccess on playing
  ---
  duration_ms: 0.794787
  type: 'test'
  ...
# Subtest: createSession returns a PlaybackSession
ok 5 - createSession returns a PlaybackSession
  ---
  duration_ms: 0.490473
  type: 'test'
  ...
# Subtest: PlaybackSession Flow
    # Subtest: URL Probe Success starts playback
    ok 1 - URL Probe Success starts playback
      ---
      duration_ms: 9.049683
      type: 'test'
      ...
    # Subtest: URL Probe Failure triggers fallback
    ok 2 - URL Probe Failure triggers fallback
      ---
      duration_ms: 2.692011
      type: 'test'
      ...
    # Subtest: Watchdog Stall triggers fallback
    ok 3 - Watchdog Stall triggers fallback
      ---
      duration_ms: 3.502259
      type: 'test'
      ...
    1..3
ok 6 - PlaybackSession Flow
  ---
  duration_ms: 15.888794
  type: 'suite'
  ...
1..6

ok 1 - PlaybackService

duration_ms: 120.01608 type: 'suite' ... 1..1

tests 8

suites 2

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 135.797237

→ 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.935775
      type: 'test'
      ...
    # Subtest: should fetch profiles for provided pubkeys
    ok 2 - should fetch profiles for provided pubkeys
      ---
      duration_ms: 2.534853
      type: 'test'
      ...
    # Subtest: should handle relay failures gracefully
    ok 3 - should handle relay failures gracefully
      ---
      duration_ms: 0.677413
      type: 'test'
      ...
    # Subtest: should parse profile content correctly
    ok 4 - should parse profile content correctly
      ---
      duration_ms: 0.840095
      type: 'test'
      ...
    # Subtest: should deduplicate concurrent requests (inflight)
    ok 5 - should deduplicate concurrent requests (inflight)
      ---
      duration_ms: 7.512188
      type: 'test'
      ...
    1..5
ok 1 - fetchProfileMetadataBatch
  ---
  duration_ms: 14.741137
  type: 'suite'
  ...
# Subtest: fetchProfileMetadata
    # Subtest: should return null for invalid pubkey
    ok 1 - should return null for invalid pubkey
      ---
      duration_ms: 0.50778
      type: 'test'
      ...
    # Subtest: should return single profile result
    ok 2 - should return single profile result
      ---
      duration_ms: 0.611886
      type: 'test'
      ...
    1..2
ok 2 - fetchProfileMetadata
  ---
  duration_ms: 1.654292
  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.477155
      type: 'test'
      ...
    # Subtest: should create a subscription via relaySubscriptionService
    ok 2 - should create a subscription via relaySubscriptionService
      ---
      duration_ms: 14.099389
      type: 'test'
      ...
    # Subtest: should handle onProfile callback
    ok 3 - should handle onProfile callback
      ---
      duration_ms: 1.122604
      type: 'test'
      ...
    1..3
ok 3 - ensureProfileMetadataSubscription
  ---
  duration_ms: 16.263852
  type: 'suite'
  ...
1..3

ok 1 - profileMetadataService

duration_ms: 33.608234 type: 'suite' ... 1..1

tests 10

suites 4

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 48.863092

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

Subtest: js/services/r2Service.js

# Subtest: Default Settings
ok 1 - Default Settings
  ---
  duration_ms: 2.480135
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Valid Input
ok 2 - handleCloudflareSettingsSubmit - Valid Input
  ---
  duration_ms: 0.665759
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Invalid S3 URL
ok 3 - handleCloudflareSettingsSubmit - Invalid S3 URL
  ---
  duration_ms: 1.20806
  type: 'test'
  ...
# Subtest: handleCloudflareSettingsSubmit - Missing Fields
ok 4 - handleCloudflareSettingsSubmit - Missing Fields
  ---
  duration_ms: 0.697357
  type: 'test'
  ...
# Subtest: ensureBucketConfigForNpub - Uses Explicit Credentials
ok 5 - ensureBucketConfigForNpub - Uses Explicit Credentials
  ---
  duration_ms: 0.791656
  type: 'test'
  ...
# Subtest: ensureBucketConfigForNpub - Uses Meta Bucket when Missing
ok 6 - ensureBucketConfigForNpub - Uses Meta Bucket when Missing
  ---
  duration_ms: 1.992289
  type: 'test'
  ...
# Subtest: resolveConnection - Maps meta bucket into settings
ok 7 - resolveConnection - Maps meta bucket into settings
  ---
  duration_ms: 0.818243
  type: 'test'
  ...
# Subtest: resolveConnection - Returns null without StorageService entries
ok 8 - resolveConnection - Returns null without StorageService entries
  ---
  duration_ms: 0.357226
  type: 'test'
  ...
1..8

ok 1 - js/services/r2Service.js

duration_ms: 12.558132 type: 'test' ... 1..1

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 27.911519

→ 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.695841 type: 'test' ...

Subtest: RelayHealthService: manages telemetry opt-in

ok 2 - RelayHealthService: manages telemetry opt-in

duration_ms: 0.351381 type: 'test' ...

Subtest: RelayHealthService: getRelayUrls fetches from relayManager

ok 3 - RelayHealthService: getRelayUrls fetches from relayManager

duration_ms: 1.188826 type: 'test' ...

Subtest: RelayHealthService: ensureRelayState creates default state

ok 4 - RelayHealthService: ensureRelayState creates default state

duration_ms: 0.320629 type: 'test' ...

Subtest: RelayHealthService: checkRelay success flow

ok 5 - RelayHealthService: checkRelay success flow

duration_ms: 0.949257 type: 'test' ...

Subtest: RelayHealthService: checkRelay failure flow

ok 6 - RelayHealthService: checkRelay failure flow

duration_ms: 0.546456 type: 'test' ...

Subtest: RelayHealthService: checkRelay handles missing nostrClient

ok 7 - RelayHealthService: checkRelay handles missing nostrClient

duration_ms: 0.305613 type: 'test' ...

Subtest: RelayHealthService: refresh checks all relays

ok 8 - RelayHealthService: refresh checks all relays

duration_ms: 0.768251 type: 'test' ...

Subtest: RelayHealthService: emits telemetry if opted in

ok 9 - RelayHealthService: emits telemetry if opted in

duration_ms: 0.922039 type: 'test' ...

Subtest: RelayHealthService: checkRelay times out after DEFAULT_TIMEOUT_MS

ok 10 - RelayHealthService: checkRelay times out after DEFAULT_TIMEOUT_MS

duration_ms: 10.11593 type: 'test' ...

Subtest: RelayHealthService: failure threshold triggers user warning

ok 11 - RelayHealthService: failure threshold triggers user warning

duration_ms: 1.038749 type: 'test' ...

Subtest: RelayHealthService: user warning respects cooldown

ok 12 - RelayHealthService: user warning respects cooldown

duration_ms: 0.635137 type: 'test' ...

Subtest: RelayHealthService: relay disconnect/error events trigger failure

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

duration_ms: 0.355538 type: 'test' ...

Subtest: RelayHealthService: integrates with nostrClient.markRelayUnreachable

ok 14 - RelayHealthService: integrates with nostrClient.markRelayUnreachable

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

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 5016.17072

→ 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.597115
      type: 'test'
      ...
    # Subtest: should throw on missing required fields
    ok 2 - should throw on missing required fields
      ---
      duration_ms: 0.821689
      type: 'test'
      ...
    1..2
ok 1 - validateS3Connection
  ---
  duration_ms: 3.682087
  type: 'suite'
  ...
# Subtest: getCorsOrigins
    # Subtest: should return current origin
    ok 1 - should return current origin
      ---
      duration_ms: 91.131937
      type: 'test'
      ...
    # Subtest: should handle localhost
    ok 2 - should handle localhost
      ---
      duration_ms: 20.030319
      type: 'test'
      ...
    1..2
ok 2 - getCorsOrigins
  ---
  duration_ms: 111.922757
  type: 'suite'
  ...
1..2

ok 1 - s3Service

duration_ms: 116.695473 type: 'suite' ... 1..1

tests 4

suites 3

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 133.422169

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

Subtest: S3UploadService

# Subtest: constructor initializes listeners
ok 1 - constructor initializes listeners
  ---
  duration_ms: 2.564365
  type: 'test'
  ...
# Subtest: Event Emitter Logic
ok 2 - Event Emitter Logic
  ---
  duration_ms: 2.762833
  type: 'test'
  ...
# Subtest: emit handles listener errors safely
ok 3 - emit handles listener errors safely
  ---
  duration_ms: 2.056087
  type: 'test'
  ...
# Subtest: verifyConnection
ok 4 - verifyConnection
  ---
  duration_ms: 0.976432
  type: 'test'
  ...
# Subtest: prepareUpload
ok 5 - prepareUpload
  ---
  duration_ms: 0.824954
  type: 'test'
  ...
# Subtest: uploadFile
    # Subtest: validates parameters
    ok 1 - validates parameters
      ---
      duration_ms: 1.649245
      type: 'test'
      ...
    # Subtest: uploads successfully
    ok 2 - uploads successfully
      ---
      duration_ms: 1.858156
      type: 'test'
      ...
    1..2
ok 6 - uploadFile
  ---
  duration_ms: 4.962254
  type: 'test'
  ...
# Subtest: uploadVideo
    # Subtest: fails if npub missing
    ok 1 - fails if npub missing
      ---
      duration_ms: 1.937778
      type: 'test'
      ...
    # Subtest: fails if title missing
    ok 2 - fails if title missing
      ---
      duration_ms: 0.960833
      type: 'test'
      ...
    # Subtest: fails if file missing
    ok 3 - fails if file missing
      ---
      duration_ms: 0.461342
      type: 'test'
      ...
    # Subtest: successful upload flow
    ok 4 - successful upload flow
      ---
      duration_ms: 4.218528
      type: 'test'
      ...
    # Subtest: uploads thumbnail if provided
    ok 5 - uploads thumbnail if provided
      ---
      duration_ms: 1.159831
      type: 'test'
      ...
    # Subtest: handles validation errors from normalizeVideoNotePayload
    ok 6 - handles validation errors from normalizeVideoNotePayload
      ---
      duration_ms: 0.743793
      type: 'test'
      ...
    # Subtest: handles upload exception
    ok 7 - handles upload exception
      ---
      duration_ms: 0.924952
      type: 'test'
      ...
    1..7
ok 7 - uploadVideo
  ---
  duration_ms: 12.935612
  type: 'test'
  ...
1..7

ok 1 - S3UploadService

duration_ms: 32.245287 type: 'test' ... 1..1

tests 17

suites 0

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 72.549465

→ 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: 3.159272
  type: 'test'
  ...
# Subtest: should apply seeds when accessControl is ready
ok 2 - should apply seeds when accessControl is ready
  ---
  duration_ms: 1.526332
  type: 'test'
  ...
# Subtest: should subscribe to accessControl changes
ok 3 - should subscribe to accessControl changes
  ---
  duration_ms: 1.456407
  type: 'test'
  ...
# Subtest: should handle accessControl timeout and apply seeds anyway
ok 4 - should handle accessControl timeout and apply seeds anyway
  ---
  duration_ms: 4.377211
  type: 'test'
  ...
# Subtest: should recompute summaries after applying seeds
ok 5 - should recompute summaries after applying seeds
  ---
  duration_ms: 0.745764
  type: 'test'
  ...
# Subtest: should wait for relays if hydration fails initially
ok 6 - should wait for relays if hydration fails initially
  ---
  duration_ms: 301.874042
  type: 'test'
  ...
1..6

ok 1 - trustBootstrap

duration_ms: 316.550979 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3777.896337

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

Subtest: buildShareEvent preserves share content

ok 1 - buildShareEvent preserves share content

duration_ms: 2.132327 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.876586 type: 'test' ...

Subtest: buildShareEvent includes sanitized relay hints

ok 3 - buildShareEvent includes sanitized relay hints

duration_ms: 0.736764 type: 'test' ...

Subtest: buildShareEvent tolerates missing optional fields

ok 4 - buildShareEvent tolerates missing optional fields

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.223226

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

Subtest: ShareNostrController

# Subtest: handleShare shares video successfully
ok 1 - handleShare shares video successfully
  ---
  duration_ms: 2.193574
  type: 'test'
  ...
# Subtest: handleShare throws error if missing video details
ok 2 - handleShare throws error if missing video details
  ---
  duration_ms: 1.072782
  type: 'test'
  ...
# Subtest: openModal shows error if no video
ok 3 - openModal shows error if no video
  ---
  duration_ms: 0.688385
  type: 'test'
  ...
# Subtest: openModal opens modal with correct payload
ok 4 - openModal opens modal with correct payload
  ---
  duration_ms: 0.455611
  type: 'test'
  ...
1..4

ok 1 - ShareNostrController

duration_ms: 6.964538 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.486102

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

Subtest: queueSignEvent executes successfully

ok 1 - queueSignEvent executes successfully

duration_ms: 2.077421 type: 'test' ...

Subtest: queueSignEvent executes sequentially for same signer

ok 2 - queueSignEvent executes sequentially for same signer

duration_ms: 104.012452 type: 'test' ...

Subtest: queueSignEvent executes concurrently for different signers

ok 3 - queueSignEvent executes concurrently for different signers

duration_ms: 51.347356 type: 'test' ...

Subtest: queueSignEvent times out

ok 4 - queueSignEvent times out

duration_ms: 52.109866 type: 'test' ...

Subtest: queueSignEvent normalizes errors

ok 5 - queueSignEvent normalizes errors

duration_ms: 0.604094 type: 'test' ...

Subtest: queueSignEvent handles signer disconnect

ok 6 - queueSignEvent handles signer disconnect

duration_ms: 0.632026 type: 'test' ...

Subtest: queueSignEvent handles missing signer

ok 7 - queueSignEvent handles missing signer

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

tests 7

suites 0

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 368.752793

→ 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: 2.678511
  type: 'test'
  ...
# Subtest: subscribeToAppStateKey fires on change
ok 2 - subscribeToAppStateKey fires on change
  ---
  duration_ms: 0.758807
  type: 'test'
  ...
# Subtest: subscribeToAppState fires on any change
ok 3 - subscribeToAppState fires on any change
  ---
  duration_ms: 0.584553
  type: 'test'
  ...
# Subtest: setModalState updates state and notifies
ok 4 - setModalState updates state and notifies
  ---
  duration_ms: 0.686928
  type: 'test'
  ...
# Subtest: resetAppState clears everything
ok 5 - resetAppState clears everything
  ---
  duration_ms: 0.697662
  type: 'test'
  ...
# Subtest: setVideosMap only accepts Maps or null
ok 6 - setVideosMap only accepts Maps or null
  ---
  duration_ms: 0.355765
  type: 'test'
  ...
1..6

ok 1 - AppState

duration_ms: 7.554153 type: 'suite' ... 1..1

tests 6

suites 1

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 24.958854

→ 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.43524 type: 'test' ...

Subtest: loadSavedProfilesFromStorage retains custom provider authType

ok 2 - loadSavedProfilesFromStorage retains custom provider authType

duration_ms: 0.70279 type: 'test' ...

Subtest: loadSavedProfilesFromStorage migrates missing authType to nip07

ok 3 - loadSavedProfilesFromStorage migrates missing authType to nip07

duration_ms: 0.374838 type: 'test' ...

Subtest: loadSavedProfilesFromStorage trims stored authType values

ok 4 - loadSavedProfilesFromStorage trims stored authType values

duration_ms: 0.370792 type: 'test' ...

Subtest: loadSavedProfilesFromStorage ignores legacy userPubKey

ok 5 - loadSavedProfilesFromStorage ignores legacy userPubKey

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 20.01787

→ 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.377923
  type: 'test'
  ...
# Subtest: Active Profile Pubkey
ok 2 - Active Profile Pubkey
  ---
  duration_ms: 0.757534
  type: 'test'
  ...
# Subtest: URL Health Caching
ok 3 - URL Health Caching
  ---
  duration_ms: 0.687948
  type: 'test'
  ...
# Subtest: URL Health Expiration
ok 4 - URL Health Expiration
  ---
  duration_ms: 10.540378
  type: 'test'
  ...
# Subtest: Moderation Settings
ok 5 - Moderation Settings
  ---
  duration_ms: 1.568199
  type: 'test'
  ...
# Subtest: Legacy Moderation Overrides Support
    # Subtest: ignores legacy v1 overrides
    ok 1 - ignores legacy v1 overrides
      ---
      duration_ms: 0.454616
      type: 'test'
      ...
    # Subtest: loads v2 overrides
    ok 2 - loads v2 overrides
      ---
      duration_ms: 0.580547
      type: 'test'
      ...
    1..2
ok 6 - Legacy Moderation Overrides Support
  ---
  duration_ms: 2.099853
  type: 'test'
  ...
1..6

ok 1 - js/state/cache.js

duration_ms: 21.378961 type: 'test' ... 1..1

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.158265

→ 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: 3.116478 type: 'test' ...

Subtest: ProfileCache: resolves storage keys correctly

ok 2 - ProfileCache: resolves storage keys correctly

duration_ms: 0.624776 type: 'test' ...

Subtest: ProfileCache: persists data to localStorage and memory

ok 3 - ProfileCache: persists data to localStorage and memory

duration_ms: 1.763569 type: 'test' ...

Subtest: ProfileCache: loads from localStorage if memory miss

ok 4 - ProfileCache: loads from localStorage if memory miss

duration_ms: 0.405332 type: 'test' ...

Subtest: ProfileCache: handles active profile switching

ok 5 - ProfileCache: handles active profile switching

duration_ms: 1.003551 type: 'test' ...

Subtest: ProfileCache: setProfile normalizes and saves

ok 6 - ProfileCache: setProfile normalizes and saves

duration_ms: 1.03522 type: 'test' ...

Subtest: ProfileCache: emits events on active profile change

ok 7 - ProfileCache: emits events on active profile change

duration_ms: 0.558421 type: 'test' ...

Subtest: ProfileCache: respects TTL expiration

ok 8 - ProfileCache: respects TTL expiration

duration_ms: 2.105731 type: 'test' ...

Subtest: ProfileCache: setProfile sanitizes XSS in media URLs

ok 9 - ProfileCache: setProfile sanitizes XSS in media URLs

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

tests 9

suites 0

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.512329

→ 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.902163 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.372173 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 16.932335

→ 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: 12.154388
  type: 'test'
  ...
ok 2 - unlock() generates and stores master key with NIP-44
duration_ms: 12.314793
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.93135
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.506485
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.193052
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: 5.047859
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.706204
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.715082
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.248761
type: 'test'
...
# Subtest: getConnection() throws if locked
ok 10 - getConnection() throws if locked
---
duration_ms: 1.342252
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.298751
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: 4.128702
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.840854
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: 69.250327 type: 'suite' ... 1..1

tests 13

suites 1

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 78.793387

→ Running tests/subscriptions-manager.test.mjs [bitvid] [nostr] Relay backoff applied. { relay: 'wss://relay-b.example', backoffMs: 1000, failureCount: 1, retryAt: 1771245694442, 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.159943 type: 'test' ...

Subtest: loadSubscriptions queries the correct subscription list kind

ok 2 - loadSubscriptions queries the correct subscription list kind

duration_ms: 1.106788 type: 'test' ...

Subtest: loadSubscriptions falls back to nip44 when hinted

ok 3 - loadSubscriptions falls back to nip44 when hinted

duration_ms: 2.081124 type: 'test' ...

Subtest: loadSubscriptions handles nip44.v2 decryptors

ok 4 - loadSubscriptions handles nip44.v2 decryptors

duration_ms: 1.346267 type: 'test' ...

Subtest: loadSubscriptions prefers nip44 decryptors when both are available

ok 5 - loadSubscriptions prefers nip44 decryptors when both are available

duration_ms: 1.508213 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.338598 type: 'test' ...

Subtest: loadSubscriptions retries permission-required decrypts only when enabled

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

duration_ms: 1.318789 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: 97.147935 type: 'test' ...

Subtest: ensureLoaded memoizes concurrent loads

ok 9 - ensureLoaded memoizes concurrent loads

duration_ms: 9.436947 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.985831 type: 'test' ...

Subtest: publishSubscriptionList prefers nip44 encryption when available

ok 11 - publishSubscriptionList prefers nip44 encryption when available

duration_ms: 1.07986 type: 'test' ...

Subtest: publishSubscriptionList falls back to nip04 when nip44 fails

ok 12 - publishSubscriptionList falls back to nip04 when nip44 fails

duration_ms: 1.060588 type: 'test' ...

Subtest: renderSameGridStyle shows empty state message

ok 13 - renderSameGridStyle shows empty state message

duration_ms: 14.122844 type: 'test' ...

Subtest: renderSameGridStyle forwards moderation badge actions to the application

ok 14 - renderSameGridStyle forwards moderation badge actions to the application

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

tests 14

suites 0

pass 14

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 6027.619613

→ 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.690546 type: 'test' ...

Subtest: identifies HTTPS requirement errors

ok 2 - identifies HTTPS requirement errors

duration_ms: 0.217528 type: 'test' ...

Subtest: identifies disabled service worker errors

ok 3 - identifies disabled service worker errors

duration_ms: 0.228681 type: 'test' ...

Subtest: identifies Brave specific guidance

ok 4 - identifies Brave specific guidance

duration_ms: 0.226709 type: 'test' ...

Subtest: identifies blocked script errors

ok 5 - identifies blocked script errors

duration_ms: 0.203939 type: 'test' ...

Subtest: identifies controller claim timeout

ok 6 - identifies controller claim timeout

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

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.530198

→ 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.123937
  type: 'test'
  ...
# Subtest: returns the original element without mutations
ok 2 - returns the original element without mutations
  ---
  duration_ms: 86.864792
  type: 'test'
  ...
# Subtest: provides an empty, frozen fallback map
ok 3 - provides an empty, frozen fallback map
  ---
  duration_ms: 1.387215
  type: 'test'
  ...
# Subtest: no-ops when removing styles
ok 4 - no-ops when removing styles
  ---
  duration_ms: 11.595862
  type: 'test'
  ...
1..4

ok 1 - torrent/ui/styleHelpers

duration_ms: 103.196223 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 121.82895

→ 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: 367.09713
  type: 'test'
  ...
1..1

ok 1 - torrent/ui/toastService

duration_ms: 368.919238 type: 'suite' ... 1..1

tests 1

suites 1

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 378.873057

→ 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: 90.628873 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 102.773198

→ 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: 105.093855 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 115.744277

→ 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: 138.558218 type: 'test' ...

Subtest: HashtagStripHelper falls back to window resize with RAF

ok 2 - HashtagStripHelper falls back to window resize with RAF

duration_ms: 20.927969 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: 20.262042 type: 'test' ...

Subtest: HashtagStripHelper handles teardown correctly

ok 4 - HashtagStripHelper handles teardown correctly

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 218.141404

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

Subtest: createVideoMoreMenuPanel - basic rendering

ok 1 - createVideoMoreMenuPanel - basic rendering

duration_ms: 127.152654 type: 'test' ...

Subtest: createVideoMoreMenuPanel - pointer info rendering

ok 2 - createVideoMoreMenuPanel - pointer info rendering

duration_ms: 16.370495 type: 'test' ...

Subtest: createVideoMoreMenuPanel - mirror logic

ok 3 - createVideoMoreMenuPanel - mirror logic

duration_ms: 14.662937 type: 'test' ...

Subtest: createVideoMoreMenuPanel - blacklist logic

ok 4 - createVideoMoreMenuPanel - blacklist logic

duration_ms: 25.256004 type: 'test' ...

Subtest: createVideoShareMenuPanel - permission logic

ok 5 - createVideoShareMenuPanel - permission logic

duration_ms: 15.418857 type: 'test' ...

Subtest: createVideoShareMenuPanel - magnet/cdn logic

ok 6 - createVideoShareMenuPanel - magnet/cdn logic

duration_ms: 11.208162 type: 'test' ...

Subtest: createChannelProfileMenuPanel - basic actions

ok 7 - createChannelProfileMenuPanel - basic actions

duration_ms: 11.732969 type: 'test' ...

Subtest: createVideoSettingsMenuPanel - capabilities

ok 8 - createVideoSettingsMenuPanel - capabilities

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

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 250.665172

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

Subtest: derivePointerKeyFromInput

# Subtest: derives key from string
ok 1 - derives key from string
  ---
  duration_ms: 1.573985
  type: 'test'
  ...
# Subtest: derives key from array
ok 2 - derives key from array
  ---
  duration_ms: 0.451704
  type: 'test'
  ...
# Subtest: derives key from object
ok 3 - derives key from object
  ---
  duration_ms: 0.431555
  type: 'test'
  ...
# Subtest: handles empty/invalid input
ok 4 - handles empty/invalid input
  ---
  duration_ms: 0.228894
  type: 'test'
  ...
1..4

ok 1 - derivePointerKeyFromInput

duration_ms: 5.202554 type: 'test' ...

Subtest: formatViewCountLabel

# Subtest: formats numbers
ok 1 - formats numbers
  ---
  duration_ms: 0.471897
  type: 'test'
  ...
1..1

ok 2 - formatViewCountLabel

duration_ms: 0.856837 type: 'test' ...

Subtest: getViewCountLabel

# Subtest: returns formatted count
ok 1 - returns formatted count
  ---
  duration_ms: 0.570812
  type: 'test'
  ...
# Subtest: handles partial
ok 2 - handles partial
  ---
  duration_ms: 0.730053
  type: 'test'
  ...
# Subtest: handles hydrating
ok 3 - handles hydrating
  ---
  duration_ms: 0.424039
  type: 'test'
  ...
1..3

ok 3 - getViewCountLabel

duration_ms: 3.192884 type: 'test' ...

Subtest: buildSimilarCardIdentity

# Subtest: uses overrides
ok 1 - uses overrides
  ---
  duration_ms: 1.716779
  type: 'test'
  ...
# Subtest: uses video author
ok 2 - uses video author
  ---
  duration_ms: 0.790695
  type: 'test'
  ...
# Subtest: derives npub from pubkey
ok 3 - derives npub from pubkey
  ---
  duration_ms: 0.224491
  type: 'test'
  ...
1..3

ok 4 - buildSimilarCardIdentity

duration_ms: 3.294801 type: 'test' ...

Subtest: prepareSimilarVideoCard

# Subtest: wires up onPlay
ok 1 - wires up onPlay
  ---
  duration_ms: 0.454367
  type: 'test'
  ...
1..1

ok 5 - prepareSimilarVideoCard

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

tests 17

suites 0

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 41.922607

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

Subtest: CreatorProfileController

# Subtest: resolveCreatorProfileFromSources prioritizes cached profile
ok 1 - resolveCreatorProfileFromSources prioritizes cached profile
  ---
  duration_ms: 2.697825
  type: 'test'
  ...
# Subtest: resolveCreatorProfileFromSources falls back to fetched profile
ok 2 - resolveCreatorProfileFromSources falls back to fetched profile
  ---
  duration_ms: 0.343455
  type: 'test'
  ...
# Subtest: decorateVideoCreatorIdentity modifies video object
ok 3 - decorateVideoCreatorIdentity modifies video object
  ---
  duration_ms: 0.56352
  type: 'test'
  ...
# Subtest: fetchModalCreatorProfile fetches profile and updates modal
ok 4 - fetchModalCreatorProfile fetches profile and updates modal
  ---
  duration_ms: 1.115325
  type: 'test'
  ...
1..4

ok 1 - CreatorProfileController

duration_ms: 6.817117 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 19.407095

→ 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: 25.364034
  type: 'test'
  ...
# Subtest: formats compact amounts correctly
ok 2 - formats compact amounts correctly
  ---
  duration_ms: 0.791273
  type: 'test'
  ...
# Subtest: handles zero and small numbers
ok 3 - handles zero and small numbers
  ---
  duration_ms: 0.64194
  type: 'test'
  ...
# Subtest: handles invalid inputs gracefully
ok 4 - handles invalid inputs gracefully
  ---
  duration_ms: 0.607358
  type: 'test'
  ...
# Subtest: handles string number inputs
ok 5 - handles string number inputs
  ---
  duration_ms: 0.283334
  type: 'test'
  ...
1..5

ok 1 - formatZapAmount

duration_ms: 29.430081 type: 'suite' ...

Subtest: aggregateZapTotals

# Subtest: aggregates totals correctly
ok 1 - aggregates totals correctly
  ---
  duration_ms: 0.473609
  type: 'test'
  ...
# Subtest: handles empty input
ok 2 - handles empty input
  ---
  duration_ms: 0.322978
  type: 'test'
  ...
# Subtest: handles non-array input
ok 3 - handles non-array input
  ---
  duration_ms: 0.479821
  type: 'test'
  ...
# Subtest: handles missing or invalid amounts
ok 4 - handles missing or invalid amounts
  ---
  duration_ms: 0.449813
  type: 'test'
  ...
# Subtest: handles missing IDs
ok 5 - handles missing IDs
  ---
  duration_ms: 0.531972
  type: 'test'
  ...
1..5

ok 2 - aggregateZapTotals

duration_ms: 2.971894 type: 'suite' ...

Subtest: normalizeZapReceipt

# Subtest: normalizes valid receipt
ok 1 - normalizes valid receipt
  ---
  duration_ms: 0.314134
  type: 'test'
  ...
# Subtest: normalizes receipt with amount fallback
ok 2 - normalizes receipt with amount fallback
  ---
  duration_ms: 0.138261
  type: 'test'
  ...
# Subtest: normalizes empty receipt
ok 3 - normalizes empty receipt
  ---
  duration_ms: 0.112128
  type: 'test'
  ...
# Subtest: normalizes undefined receipt
ok 4 - normalizes undefined receipt
  ---
  duration_ms: 0.156142
  type: 'test'
  ...
# Subtest: normalizes invalid amount
ok 5 - normalizes invalid amount
  ---
  duration_ms: 0.117023
  type: 'test'
  ...
1..5

ok 3 - normalizeZapReceipt

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

tests 15

suites 3

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 58.227104

→ 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.091258
  type: 'test'
  ...
# Subtest: handleRepostAction - should call repostEvent with correct parameters
ok 2 - handleRepostAction - should call repostEvent with correct parameters
  ---
  duration_ms: 0.845447
  type: 'test'
  ...
# Subtest: handleRepostAction - should handle failure from repostEvent
ok 3 - handleRepostAction - should handle failure from repostEvent
  ---
  duration_ms: 0.680881
  type: 'test'
  ...
# Subtest: handleRepostAction - should use currentVideoPointer when in modal context
ok 4 - handleRepostAction - should use currentVideoPointer when in modal context
  ---
  duration_ms: 0.724121
  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.072252
  type: 'test'
  ...
# Subtest: handleMirrorAction - should call mirrorVideoEvent when URL is provided
ok 6 - handleMirrorAction - should call mirrorVideoEvent when URL is provided
  ---
  duration_ms: 0.592041
  type: 'test'
  ...
# Subtest: handleMirrorAction - should prevent mirroring private videos
ok 7 - handleMirrorAction - should prevent mirroring private videos
  ---
  duration_ms: 0.401547
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should show error if no event ID
ok 8 - handleEnsurePresenceAction - should show error if no event ID
  ---
  duration_ms: 0.748266
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should handle throttled response
ok 9 - handleEnsurePresenceAction - should handle throttled response
  ---
  duration_ms: 0.812316
  type: 'test'
  ...
# Subtest: handleEnsurePresenceAction - should show success on successful rebroadcast
ok 10 - handleEnsurePresenceAction - should show success on successful rebroadcast
  ---
  duration_ms: 0.479307
  type: 'test'
  ...
1..10

ok 1 - EngagementController

duration_ms: 13.254322 type: 'test' ... 1..1

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.05774

→ 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: 172.448164 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.167782 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 203.633274

→ 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: 614.231783 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: 92.461431 type: 'test' ...

Subtest: closes on outside pointer events and restores focus

ok 3 - closes on outside pointer events and restores focus

duration_ms: 51.448242 type: 'test' ...

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

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

duration_ms: 69.891274 type: 'test' ...

Subtest: escape closes the popover and restores trigger focus

ok 5 - escape closes the popover and restores trigger focus

duration_ms: 47.721157 type: 'test' ...

Subtest: close respects restoreFocus option for contextual menus

ok 6 - close respects restoreFocus option for contextual menus

duration_ms: 40.003393 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: 111.008688 type: 'test' ...

Subtest: applies token-based sizing and arrow positioning

ok 8 - applies token-based sizing and arrow positioning

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

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1141.917014

→ 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: 151.070205 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: 36.00139 type: 'test' ...

Subtest: guest fallback uses config moderation defaults

ok 3 - guest fallback uses config moderation defaults

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 222.045711

→ 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: 148.704112 type: 'test' ...

Subtest: handleAddProfile prevents duplicates

ok 2 - handleAddProfile prevents duplicates

duration_ms: 29.116613 type: 'test' ...

Subtest: handleAddProfile handles missing login result

ok 3 - handleAddProfile handles missing login result

duration_ms: 18.163257 type: 'test' ...

Subtest: handleAddProfile handles errors gracefully

ok 4 - handleAddProfile handles errors gracefully

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 227.314335

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

Subtest: ReactionController

# Subtest: should subscribe to reactions
ok 1 - should subscribe to reactions
  ---
  duration_ms: 2.863685
  type: 'test'
  ...
# Subtest: should handle reaction update from subscription
ok 2 - should handle reaction update from subscription
  ---
  duration_ms: 0.698884
  type: 'test'
  ...
# Subtest: should apply optimistic update on handleReaction
ok 3 - should apply optimistic update on handleReaction
  ---
  duration_ms: 0.984459
  type: 'test'
  ...
# Subtest: should rollback optimistic update on publish failure
ok 4 - should rollback optimistic update on publish failure
  ---
  duration_ms: 0.903191
  type: 'test'
  ...
# Subtest: should not react if user not logged in
ok 5 - should not react if user not logged in
  ---
  duration_ms: 0.99017
  type: 'test'
  ...
1..5

ok 1 - ReactionController

duration_ms: 8.401115 type: 'suite' ... 1..1

tests 5

suites 1

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 23.132345

→ 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: 263.455222 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: 36.880098 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: 19.939003 type: 'test' ...

Subtest: author identity fields render supplied values and datasets

ok 4 - author identity fields render supplied values and datasets

duration_ms: 15.088562 type: 'test' ...

Subtest: view counter wiring exposes pointer datasets

ok 5 - view counter wiring exposes pointer datasets

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 372.442124

→ 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.761748
      type: 'test'
      ...
    # Subtest: should return empty string if no 'd' tag
    ok 2 - should return empty string if no 'd' tag
      ---
      duration_ms: 0.559558
      type: 'test'
      ...
    1..2
ok 1 - extractDTagValue
  ---
  duration_ms: 3.294584
  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.897936
      type: 'test'
      ...
    # Subtest: should compute similar candidates based on tags
    ok 2 - should compute similar candidates based on tags
      ---
      duration_ms: 1.596907
      type: 'test'
      ...
    # Subtest: should exclude blocked authors
    ok 3 - should exclude blocked authors
      ---
      duration_ms: 0.697259
      type: 'test'
      ...
    # Subtest: should prioritize higher shared tag count
    ok 4 - should prioritize higher shared tag count
      ---
      duration_ms: 0.733433
      type: 'test'
      ...
    1..4
ok 2 - computeCandidates
  ---
  duration_ms: 5.376182
  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.491241
      type: 'test'
      ...
    # Subtest: should set similar content if matches found
    ok 2 - should set similar content if matches found
      ---
      duration_ms: 1.092638
      type: 'test'
      ...
    # Subtest: should clear similar content if no matches found
    ok 3 - should clear similar content if no matches found
      ---
      duration_ms: 0.928741
      type: 'test'
      ...
    1..3
ok 3 - updateModal
  ---
  duration_ms: 2.925663
  type: 'suite'
  ...
1..3

ok 1 - SimilarContentController

duration_ms: 12.710391 type: 'suite' ... 1..1

tests 9

suites 4

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 34.087354

→ 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: 17.044583
  type: 'test'
  ...
ok 2 - should save and retrieve an R2 connection
duration_ms: 8.576202
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: 4.664506
type: 'test'
...
# Subtest: should fail to get connection if locked
ok 4 - should fail to get connection if locked
---
duration_ms: 1.525241
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: 160.10602 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 168.581857

→ 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: 103.040294 type: 'test' ...

Subtest: renderTagPillStrip applies preference state styling

ok 2 - renderTagPillStrip applies preference state styling

duration_ms: 13.738197 type: 'test' ...

Subtest: renderTagPillStrip wires the activation callback

ok 3 - renderTagPillStrip wires the activation callback

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 149.538227

→ 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: 93.467643 type: 'test' ...

Subtest: createTagPreferenceMenu disables actions based on membership and login

ok 2 - createTagPreferenceMenu disables actions based on membership and login

duration_ms: 14.814446 type: 'test' ...

Subtest: createTagPreferenceMenu forwards actions to callback

ok 3 - createTagPreferenceMenu forwards actions to callback

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 134.756472

→ 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: 2.527346
      type: 'test'
      ...
    # Subtest: should return existing entry if one exists
    ok 2 - should return existing entry if one exists
      ---
      duration_ms: 0.567078
      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.046058
      type: 'test'
      ...
    1..3
ok 1 - ensurePopover
  ---
  duration_ms: 5.374768
  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: 1.41141
      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.638696
      type: 'test'
      ...
    1..2
ok 2 - requestMenu
  ---
  duration_ms: 2.304333
  type: 'suite'
  ...
# Subtest: handleMenuAction
    # Subtest: should call service method and notify update
    ok 1 - should call service method and notify update
      ---
      duration_ms: 0.678013
      type: 'test'
      ...
    # Subtest: should handle error and call showError
    ok 2 - should handle error and call showError
      ---
      duration_ms: 0.828798
      type: 'test'
      ...
    1..2
ok 3 - handleMenuAction
  ---
  duration_ms: 2.159633
  type: 'suite'
  ...
# Subtest: persistPreferencesFromMenu
    # Subtest: should call service.publish
    ok 1 - should call service.publish
      ---
      duration_ms: 0.707097
      type: 'test'
      ...
    # Subtest: should reuse in-flight promise
    ok 2 - should reuse in-flight promise
      ---
      duration_ms: 0.643234
      type: 'test'
      ...
    1..2
ok 4 - persistPreferencesFromMenu
  ---
  duration_ms: 1.561474
  type: 'suite'
  ...
1..4

ok 1 - TagPreferenceMenuController

duration_ms: 12.473293 type: 'suite' ... 1..1

tests 9

suites 5

pass 9

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 33.48443

→ 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: 1.613891 type: 'test' ...

Subtest: TorrentStatusController updates video modal and calls onRemovePoster

ok 2 - TorrentStatusController updates video modal and calls onRemovePoster

duration_ms: 0.713526 type: 'test' ...

Subtest: TorrentStatusController handles missing modal gracefully

ok 3 - TorrentStatusController handles missing modal gracefully

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.854892

→ 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: 68.375592
  type: 'test'
  ...
# Subtest: should handle locked state and unlock flow
ok 2 - should handle locked state and unlock flow
  ---
  duration_ms: 41.972795
  type: 'test'
  ...
1..2

ok 1 - UploadModal Integration

duration_ms: 12327.885222 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12336.70958

→ 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: 95.37874
  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: 25.953836
  type: 'test'
  ...
# Subtest: should call resetUploads when close is called
ok 3 - should call resetUploads when close is called
  ---
  duration_ms: 19.092285
  type: 'test'
  ...
1..3

ok 1 - UploadModal Reset Logic

duration_ms: 12360.722176 type: 'suite' ... 1..1

tests 3

suites 1

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12372.206704

→ 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: 128.521844
  type: 'test'
  ...
# Subtest: probeUrl returns error for 404
ok 2 - probeUrl returns error for 404
  ---
  duration_ms: 22.69043
  type: 'test'
  ...
# Subtest: handleUrlHealthBadge updates badge
ok 3 - handleUrlHealthBadge updates badge
  ---
  duration_ms: 80.030034
  type: 'test'
  ...
# Subtest: getUrlHealthPlaceholderMarkup returns string
ok 4 - getUrlHealthPlaceholderMarkup returns string
  ---
  duration_ms: 11.041297
  type: 'test'
  ...
1..4

ok 1 - UrlHealthController

duration_ms: 244.285579 type: 'suite' ... 1..1

tests 4

suites 1

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 260.759732

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

Subtest: VideoListView renders sorted popular tag pills

duration_ms: 223.590584 type: 'test' ...

Subtest: VideoListView applies tag preference variants to popular tags

duration_ms: 55.127539 type: 'test' ...

Subtest: VideoListView hides popular tags when no tags are available

duration_ms: 34.177698 type: 'test' ...

Subtest: VideoListView setModerationBlockHandler stores callable

ok 4 - VideoListView setModerationBlockHandler stores callable

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 350.908911

→ 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: 275.304415 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 305.161708

→ 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: 2.338532
  type: 'test'
  ...
# Subtest: does not throw when videoModal is undefined
ok 2 - does not throw when videoModal is undefined
  ---
  duration_ms: 0.630409
  type: 'test'
  ...
# Subtest: does not throw when videoModal lacks resetStats
ok 3 - does not throw when videoModal lacks resetStats
  ---
  duration_ms: 0.905301
  type: 'test'
  ...
# Subtest: calls resetStats when available
ok 4 - calls resetStats when available
  ---
  duration_ms: 0.592242
  type: 'test'
  ...
1..4

ok 1 - Application.resetTorrentStats logic

duration_ms: 11.623567 type: 'test' ... 1..1

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 45.794342

→ 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: 3411.37123 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 3426.33147

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

Subtest: normalizeCommentAvatarKey handles inputs correctly

ok 1 - normalizeCommentAvatarKey handles inputs correctly

duration_ms: 2.898832 type: 'test' ...

Subtest: resolveCommentAvatarAsset resolves avatars correctly

ok 2 - resolveCommentAvatarAsset resolves avatars correctly

duration_ms: 2.618847 type: 'test' ...

Subtest: registerCommentAvatarFailure registers failures correctly

ok 3 - registerCommentAvatarFailure registers failures correctly

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 32.763292

→ 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.322955
      type: 'test'
      ...
    # Subtest: should show error if not logged in
    ok 2 - should show error if not logged in
      ---
      duration_ms: 0.941934
      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.53694
      type: 'test'
      ...
    # Subtest: should handle modal load errors
    ok 4 - should handle modal load errors
      ---
      duration_ms: 0.928347
      type: 'test'
      ...
    1..4
ok 1 - open()
  ---
  duration_ms: 5.845558
  type: 'suite'
  ...
# Subtest: handleSubmit()
    # Subtest: should handle successful submission
    ok 1 - should handle successful submission
      ---
      duration_ms: 1.051188
      type: 'test'
      ...
    # Subtest: should show error if not logged in
    ok 2 - should show error if not logged in
      ---
      duration_ms: 0.63737
      type: 'test'
      ...
    # Subtest: should handle submission errors
    ok 3 - should handle submission errors
      ---
      duration_ms: 1.482801
      type: 'test'
      ...
    1..3
ok 2 - handleSubmit()
  ---
  duration_ms: 3.799674
  type: 'suite'
  ...
1..2

ok 1 - EditModalController

duration_ms: 10.562045 type: 'suite' ... 1..1

tests 7

suites 3

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 38.753529

→ 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: 1.120815
  type: 'test'
  ...
# Subtest: resetStats is no-op and does not throw
ok 2 - resetStats is no-op and does not throw
  ---
  duration_ms: 1.49416
  type: 'test'
  ...
1..2

ok 1 - EmbedPlayerModal interface

duration_ms: 4.126891 type: 'test' ... 1..1

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.682124

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

Subtest: embed accent color configuration

ok 1 - embed accent color configuration

duration_ms: 2.536628 type: 'test' ... 1..1

tests 1

suites 0

pass 1

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 12.746782

→ 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.362723
  type: 'test'
  ...
# Subtest: redirects legacy view
ok 2 - redirects legacy view
  ---
  duration_ms: 0.314785
  type: 'test'
  ...
# Subtest: skips redundant reload
ok 3 - skips redundant reload
  ---
  duration_ms: 0.242645
  type: 'test'
  ...
1..3

ok 1 - createHashChangeHandler

duration_ms: 3.52983 type: 'test' ... 1..1

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.808185

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

Subtest: NotificationController

# Subtest: should instantiate correctly
ok 1 - should instantiate correctly
  ---
  duration_ms: 2.636695
  type: 'test'
  ...
# Subtest: showError should update text content and show container
ok 2 - showError should update text content and show container
  ---
  duration_ms: 1.022695
  type: 'test'
  ...
# Subtest: showError should hide container when msg is empty
ok 3 - showError should hide container when msg is empty
  ---
  duration_ms: 1.122351
  type: 'test'
  ...
# Subtest: updateNotificationPortalVisibility should toggle active class on portal
ok 4 - updateNotificationPortalVisibility should toggle active class on portal
  ---
  duration_ms: 1.037858
  type: 'test'
  ...
# Subtest: showSuccess should update text content and show container
ok 5 - showSuccess should update text content and show container
  ---
  duration_ms: 0.865862
  type: 'test'
  ...
# Subtest: showStatus should update text content and show container
ok 6 - showStatus should update text content and show container
  ---
  duration_ms: 0.749996
  type: 'test'
  ...
# Subtest: showStatus should handle spinner option
ok 7 - showStatus should handle spinner option
  ---
  duration_ms: 0.594115
  type: 'test'
  ...
1..7

ok 1 - NotificationController

duration_ms: 9.916821 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 28.704359

→ 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: 3.643927 type: 'test' ...

Subtest: serializeFiltersToQuery emits filter tokens in order

ok 2 - serializeFiltersToQuery emits filter tokens in order

duration_ms: 0.490893 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.54951 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.230723 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.738123

→ 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: 1.21933 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.288657 type: 'test' ... 1..2

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.062538

→ 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: 1.150956
      type: 'test'
      ...
    # Subtest: should return empty string for empty types
    ok 2 - should return empty string for empty types
      ---
      duration_ms: 0.215038
      type: 'test'
      ...
    # Subtest: should return the type with highest trusted count
    ok 3 - should return the type with highest trusted count
      ---
      duration_ms: 0.199327
      type: 'test'
      ...
    1..3
ok 1 - deriveModerationReportType
  ---
  duration_ms: 2.685617
  type: 'suite'
  ...
# Subtest: deriveModerationTrustedCount
    # Subtest: should return trusted count for specific type
    ok 1 - should return trusted count for specific type
      ---
      duration_ms: 0.431511
      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.194684
      type: 'test'
      ...
    1..2
ok 2 - deriveModerationTrustedCount
  ---
  duration_ms: 0.878162
  type: 'suite'
  ...
# Subtest: getReporterDisplayName
    # Subtest: should return name from cache if available
    ok 1 - should return name from cache if available
      ---
      duration_ms: 0.356057
      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.270859
      type: 'test'
      ...
    1..2
ok 3 - getReporterDisplayName
  ---
  duration_ms: 2.170465
  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.634592
      type: 'test'
      ...
    # Subtest: should decorate video with basic moderation
    ok 2 - should decorate video with basic moderation
      ---
      duration_ms: 0.600607
      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.547999
      type: 'test'
      ...
    1..3
ok 4 - decorateVideo
  ---
  duration_ms: 2.02299
  type: 'suite'
  ...
# Subtest: updateSettings
    # Subtest: should update moderation settings and affect decoration
    ok 1 - should update moderation settings and affect decoration
      ---
      duration_ms: 0.504933
      type: 'test'
      ...
    1..1
ok 5 - updateSettings
  ---
  duration_ms: 0.609011
  type: 'suite'
  ...
1..5

ok 1 - ModerationDecorator

duration_ms: 9.440539 type: 'suite' ... 1..1

tests 11

suites 6

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.168274

→ 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: 29.121314
  type: 'test'
  ...
# Subtest: uses configured meta.bucket when verifying public access
ok 2 - uses configured meta.bucket when verifying public access
  ---
  duration_ms: 1.816097
  type: 'test'
  ...
1..2

ok 1 - R2Service bucket selection

duration_ms: 32.503905 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 42.721615

→ Running tests/unit/ui/videoModalController.test.mjs TAP version 13

Subtest: VideoModalController bindEvents attaches listeners

ok 1 - VideoModalController bindEvents attaches listeners

duration_ms: 1.469054 type: 'test' ...

Subtest: VideoModalController handleShareNostr triggers callback

ok 2 - VideoModalController handleShareNostr triggers callback

duration_ms: 0.360093 type: 'test' ...

Subtest: VideoModalController handleCopyCdn triggers clipboard write

ok 3 - VideoModalController handleCopyCdn triggers clipboard write

duration_ms: 6.529012 type: 'test' ...

Subtest: VideoModalController handleCopyMagnet triggers callback

ok 4 - VideoModalController handleCopyMagnet triggers callback

duration_ms: 0.441862 type: 'test' ...

Subtest: VideoModalController handleSourceSwitch calls playVideoWithFallback

ok 5 - VideoModalController handleSourceSwitch calls playVideoWithFallback

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

tests 5

suites 0

pass 5

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 17.955402

→ 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: 5.324087
      type: 'test'
      ...
    # Subtest: should resolve card from object with .card property
    ok 2 - should resolve card from object with .card property
      ---
      duration_ms: 2.818013
      type: 'test'
      ...
    # Subtest: should always show card if owner is viewer
    ok 3 - should always show card if owner is viewer
      ---
      duration_ms: 1.529912
      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: 1.614845
      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: 1.690802
      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: 1.09194
      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.067253
      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: 8.063124
      type: 'test'
      ...
    1..8
ok 1 - updateVideoCardSourceVisibility
  ---
  duration_ms: 24.688617
  type: 'suite'
  ...
1..1

ok 1 - cardSourceVisibility

duration_ms: 25.563926 type: 'suite' ... 1..1

tests 8

suites 2

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 44.264933

→ 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: 1.203993
      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.335556
      type: 'test'
      ...
    # Subtest: should escape special characters
    ok 3 - should escape special characters
      ---
      duration_ms: 0.377383
      type: 'test'
      ...
    # Subtest: should escape multiple occurrences of special characters
    ok 4 - should escape multiple occurrences of special characters
      ---
      duration_ms: 0.287793
      type: 'test'
      ...
    # Subtest: should handle mixed content correctly
    ok 5 - should handle mixed content correctly
      ---
      duration_ms: 0.321212
      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.175233
      type: 'test'
      ...
    1..6
ok 1 - escapeHTML
  ---
  duration_ms: 3.999235
  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: 0.407153
      type: 'test'
      ...
    # Subtest: should do nothing if root has no querySelectorAll
    ok 2 - should do nothing if root has no querySelectorAll
      ---
      duration_ms: 0.397471
      type: 'test'
      ...
    # Subtest: should remove scripts matching the tracking pattern
    ok 3 - should remove scripts matching the tracking pattern
      ---
      duration_ms: 22.359224
      type: 'test'
      ...
    # Subtest: should not remove inline scripts (no src)
    ok 4 - should not remove inline scripts (no src)
      ---
      duration_ms: 2.621617
      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: 1.152219
      type: 'test'
      ...
    # Subtest: should remove scripts where src contains /tracking.js
    ok 6 - should remove scripts where src contains /tracking.js
      ---
      duration_ms: 0.864655
      type: 'test'
      ...
    1..6
ok 2 - removeTrackingScripts
  ---
  duration_ms: 28.612281
  type: 'suite'
  ...
1..2

ok 1 - domUtils

duration_ms: 33.598757 type: 'suite' ... 1..1

tests 12

suites 3

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 55.080459

→ 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: 0.903945
      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.280728
      type: 'test'
      ...
    # Subtest: should trim and lowercase valid hex strings
    ok 3 - should trim and lowercase valid hex strings
      ---
      duration_ms: 0.180477
      type: 'test'
      ...
    1..3
ok 1 - normalizeHexString
  ---
  duration_ms: 2.572315
  type: 'suite'
  ...
# Subtest: aliases
    # Subtest: should export normalizeHexId as an alias
    ok 1 - should export normalizeHexId as an alias
      ---
      duration_ms: 0.20727
      type: 'test'
      ...
    # Subtest: should export normalizeHexPubkey as an alias
    ok 2 - should export normalizeHexPubkey as an alias
      ---
      duration_ms: 0.144501
      type: 'test'
      ...
    1..2
ok 2 - aliases
  ---
  duration_ms: 0.629613
  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.545044
      type: 'test'
      ...
    # Subtest: should not match strings with incorrect length
    ok 2 - should not match strings with incorrect length
      ---
      duration_ms: 0.483598
      type: 'test'
      ...
    # Subtest: should not match strings with non-hex characters
    ok 3 - should not match strings with non-hex characters
      ---
      duration_ms: 0.168961
      type: 'test'
      ...
    # Subtest: should not match empty strings
    ok 4 - should not match empty strings
      ---
      duration_ms: 0.228797
      type: 'test'
      ...
    1..4
ok 3 - HEX64_REGEX
  ---
  duration_ms: 2.141199
  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.295212
      type: 'test'
      ...
    # Subtest: should return empty string for invalid hex
    ok 2 - should return empty string for invalid hex
      ---
      duration_ms: 0.122761
      type: 'test'
      ...
    # Subtest: should return normalized hex for valid inputs
    ok 3 - should return normalized hex for valid inputs
      ---
      duration_ms: 0.117866
      type: 'test'
      ...
    1..3
ok 4 - normalizeHexHash
  ---
  duration_ms: 0.823127
  type: 'suite'
  ...
1..4

ok 1 - hex utils

duration_ms: 7.225275 type: 'suite' ... 1..1

tests 12

suites 5

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 29.933696

→ 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: 2.712632
      type: 'test'
      ...
    # Subtest: returns stored settings
    ok 2 - returns stored settings
      ---
      duration_ms: 1.231902
      type: 'test'
      ...
    # Subtest: returns defaults when storage is invalid
    ok 3 - returns defaults when storage is invalid
      ---
      duration_ms: 1.030667
      type: 'test'
      ...
    # Subtest: sanitizes settings from storage
    ok 4 - sanitizes settings from storage
      ---
      duration_ms: 0.811109
      type: 'test'
      ...
    1..4
ok 1 - getLinkPreviewSettings
  ---
  duration_ms: 6.965557
  type: 'suite'
  ...
# Subtest: setLinkPreviewAutoFetch
    # Subtest: updates settings and persists to storage
    ok 1 - updates settings and persists to storage
      ---
      duration_ms: 1.153453
      type: 'test'
      ...
    # Subtest: emits an event on change
    ok 2 - emits an event on change
      ---
      duration_ms: 1.327035
      type: 'test'
      ...
    1..2
ok 2 - setLinkPreviewAutoFetch
  ---
  duration_ms: 2.932048
  type: 'suite'
  ...
# Subtest: allowLinkPreviewDomain
    # Subtest: adds a domain and persists to storage
    ok 1 - adds a domain and persists to storage
      ---
      duration_ms: 0.709342
      type: 'test'
      ...
    # Subtest: handles duplicates
    ok 2 - handles duplicates
      ---
      duration_ms: 0.689501
      type: 'test'
      ...
    # Subtest: normalizes domains
    ok 3 - normalizes domains
      ---
      duration_ms: 0.718422
      type: 'test'
      ...
    # Subtest: emits an event on change
    ok 4 - emits an event on change
      ---
      duration_ms: 0.675502
      type: 'test'
      ...
    # Subtest: respects silent option
    ok 5 - respects silent option
      ---
      duration_ms: 0.475645
      type: 'test'
      ...
    1..5
ok 3 - allowLinkPreviewDomain
  ---
  duration_ms: 3.814761
  type: 'suite'
  ...
# Subtest: isLinkPreviewDomainAllowed
    # Subtest: returns true for allowed domains
    ok 1 - returns true for allowed domains
      ---
      duration_ms: 0.599895
      type: 'test'
      ...
    # Subtest: returns false for disallowed domains
    ok 2 - returns false for disallowed domains
      ---
      duration_ms: 0.298582
      type: 'test'
      ...
    # Subtest: handles normalization
    ok 3 - handles normalization
      ---
      duration_ms: 0.645214
      type: 'test'
      ...
    # Subtest: accepts settings object as second argument
    ok 4 - accepts settings object as second argument
      ---
      duration_ms: 0.329619
      type: 'test'
      ...
    1..4
ok 4 - isLinkPreviewDomainAllowed
  ---
  duration_ms: 2.109764
  type: 'suite'
  ...
# Subtest: subscribeToLinkPreviewSettings
    # Subtest: calls callback on event emission
    ok 1 - calls callback on event emission
      ---
      duration_ms: 0.804527
      type: 'test'
      ...
    # Subtest: unsubscribes correctly
    ok 2 - unsubscribes correctly
      ---
      duration_ms: 0.731621
      type: 'test'
      ...
    1..2
ok 5 - subscribeToLinkPreviewSettings
  ---
  duration_ms: 1.686305
  type: 'suite'
  ...
1..5

ok 1 - linkPreviewSettings

duration_ms: 18.867315 type: 'suite' ... 1..1

tests 17

suites 6

pass 17

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 44.981903

→ 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: 1.411484
  type: 'test'
  ...
# Subtest: should store and retrieve values
ok 2 - should store and retrieve values
  ---
  duration_ms: 0.366526
  type: 'test'
  ...
# Subtest: should evict oldest item when limit is reached
ok 3 - should evict oldest item when limit is reached
  ---
  duration_ms: 0.267837
  type: 'test'
  ...
# Subtest: should refresh recency on access
ok 4 - should refresh recency on access
  ---
  duration_ms: 0.470936
  type: 'test'
  ...
# Subtest: should update value and refresh recency on set
ok 5 - should update value and refresh recency on set
  ---
  duration_ms: 0.638596
  type: 'test'
  ...
# Subtest: should track stats
ok 6 - should track stats
  ---
  duration_ms: 0.380277
  type: 'test'
  ...
# Subtest: should clear cache
ok 7 - should clear cache
  ---
  duration_ms: 0.258647
  type: 'test'
  ...
1..7

ok 1 - LRUCache

duration_ms: 5.774533 type: 'suite' ... 1..1

tests 7

suites 1

pass 7

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 22.413024

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

Subtest: sanitizeProfileMediaUrl handles non-string inputs

ok 1 - sanitizeProfileMediaUrl handles non-string inputs

duration_ms: 1.536618 type: 'test' ...

Subtest: sanitizeProfileMediaUrl handles empty or whitespace-only strings

ok 2 - sanitizeProfileMediaUrl handles empty or whitespace-only strings

duration_ms: 0.258488 type: 'test' ...

Subtest: sanitizeProfileMediaUrl trims whitespace and removes quotes

ok 3 - sanitizeProfileMediaUrl trims whitespace and removes quotes

duration_ms: 0.914352 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows data:image/ URLs

ok 4 - sanitizeProfileMediaUrl allows data:image/ URLs

duration_ms: 0.223616 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows blob: URLs

ok 5 - sanitizeProfileMediaUrl allows blob: URLs

duration_ms: 0.294188 type: 'test' ...

Subtest: sanitizeProfileMediaUrl rejects specific placeholder images

ok 6 - sanitizeProfileMediaUrl rejects specific placeholder images

duration_ms: 0.150491 type: 'test' ...

Subtest: sanitizeProfileMediaUrl normalizes IPFS URLs

ok 7 - sanitizeProfileMediaUrl normalizes IPFS URLs

duration_ms: 0.44059 type: 'test' ...

Subtest: sanitizeProfileMediaUrl handles protocol-relative URLs

ok 8 - sanitizeProfileMediaUrl handles protocol-relative URLs

duration_ms: 0.189419 type: 'test' ...

Subtest: sanitizeProfileMediaUrl allows relative paths

ok 9 - sanitizeProfileMediaUrl allows relative paths

duration_ms: 0.587005 type: 'test' ...

Subtest: sanitizeProfileMediaUrl adds protocol to domains and localhost

ok 10 - sanitizeProfileMediaUrl adds protocol to domains and localhost

duration_ms: 1.01817 type: 'test' ...

Subtest: sanitizeProfileMediaUrl coerces http to https except for localhost

ok 11 - sanitizeProfileMediaUrl coerces http to https except for localhost

duration_ms: 0.260749 type: 'test' ...

Subtest: sanitizeProfileMediaUrl rejects unsupported patterns

ok 12 - sanitizeProfileMediaUrl rejects unsupported patterns

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

tests 12

suites 0

pass 12

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 25.478534

→ 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.210421 type: 'test' ...

Subtest: dedupeToNewestByRoot replaces entries with non-numeric timestamps

ok 2 - dedupeToNewestByRoot replaces entries with non-numeric timestamps

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.016295

→ 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: 15.69043 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: 0.410212 type: 'test' ...

Subtest: collectVideoTags handles malformed inputs safely

ok 3 - collectVideoTags handles malformed inputs safely

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

tests 3

suites 0

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 30.120014

→ 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: 2.346948 type: 'test' ...

Subtest: resolveVideoPointer returns address pointer with videoRootId

ok 2 - resolveVideoPointer returns address pointer with videoRootId

duration_ms: 0.317513 type: 'test' ...

Subtest: resolveVideoPointer returns event pointer with fallbackEventId

ok 3 - resolveVideoPointer returns event pointer with fallbackEventId

duration_ms: 0.370463 type: 'test' ...

Subtest: resolveVideoPointer prioritizes dTag over videoRootId

ok 4 - resolveVideoPointer prioritizes dTag over videoRootId

duration_ms: 0.19418 type: 'test' ...

Subtest: resolveVideoPointer prioritizes videoRootId over fallbackEventId

ok 5 - resolveVideoPointer prioritizes videoRootId over fallbackEventId

duration_ms: 0.209167 type: 'test' ...

Subtest: resolveVideoPointer prioritizes dTag over fallbackEventId

ok 6 - resolveVideoPointer prioritizes dTag over fallbackEventId

duration_ms: 0.181354 type: 'test' ...

Subtest: resolveVideoPointer includes relay in pointer

ok 7 - resolveVideoPointer includes relay in pointer

duration_ms: 0.295932 type: 'test' ...

Subtest: resolveVideoPointer normalizes inputs

ok 8 - resolveVideoPointer normalizes inputs

duration_ms: 0.200796 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: 0.667794 type: 'test' ...

Subtest: resolveVideoPointer returns null for invalid inputs

ok 10 - resolveVideoPointer returns null for invalid inputs

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

tests 10

suites 0

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 23.384856

→ 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: 2.344952
  type: 'test'
  ...
# Subtest: returns videoRootId when present
ok 2 - returns videoRootId when present
  ---
  duration_ms: 0.195511
  type: 'test'
  ...
# Subtest: returns id when videoRootId is missing
ok 3 - returns id when videoRootId is missing
  ---
  duration_ms: 0.291021
  type: 'test'
  ...
# Subtest: returns id when videoRootId is not a string
ok 4 - returns id when videoRootId is not a string
  ---
  duration_ms: 0.22164
  type: 'test'
  ...
1..4

ok 1 - getVideoRootIdentifier

duration_ms: 4.69804 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: 0.314736
  type: 'test'
  ...
# Subtest: updates rootCreatedAt for matching video ID
ok 2 - updates rootCreatedAt for matching video ID
  ---
  duration_ms: 0.191442
  type: 'test'
  ...
# Subtest: updates rootCreatedAt for other videos with same rootId
ok 3 - updates rootCreatedAt for other videos with same rootId
  ---
  duration_ms: 0.401925
  type: 'test'
  ...
# Subtest: skips invalid stored objects in map
ok 4 - skips invalid stored objects in map
  ---
  duration_ms: 0.320972
  type: 'test'
  ...
1..4

ok 2 - applyRootTimestampToVideosMap

duration_ms: 2.652645 type: 'test' ...

Subtest: syncActiveVideoRootTimestamp

# Subtest: returns false for invalid timestamp
ok 1 - returns false for invalid timestamp
  ---
  duration_ms: 0.259431
  type: 'test'
  ...
# Subtest: returns false for invalid activeVideo
ok 2 - returns false for invalid activeVideo
  ---
  duration_ms: 0.140832
  type: 'test'
  ...
# Subtest: returns false if activeVideo has no root identifier
ok 3 - returns false if activeVideo has no root identifier
  ---
  duration_ms: 0.115722
  type: 'test'
  ...
# Subtest: returns false if rootId mismatch
ok 4 - returns false if rootId mismatch
  ---
  duration_ms: 0.129447
  type: 'test'
  ...
# Subtest: returns false if timestamp already matches
ok 5 - returns false if timestamp already matches
  ---
  duration_ms: 0.117935
  type: 'test'
  ...
# Subtest: updates activeVideo and displayTags
ok 6 - updates activeVideo and displayTags
  ---
  duration_ms: 0.545261
  type: 'test'
  ...
# Subtest: calls videoModal.updateMetadata if provided
ok 7 - calls videoModal.updateMetadata if provided
  ---
  duration_ms: 0.224737
  type: 'test'
  ...
1..7

ok 3 - syncActiveVideoRootTimestamp

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

tests 18

suites 0

pass 18

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 31.428027

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

Subtest: validateInstanceConfig succeeds with valid configuration

ok 1 - validateInstanceConfig succeeds with valid configuration

duration_ms: 2.077452 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: 1.104098 type: 'test' ...

Subtest: validateInstanceConfig throws when ADMIN_SUPER_NPUB is empty

ok 3 - validateInstanceConfig throws when ADMIN_SUPER_NPUB is empty

duration_ms: 0.241062 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.200308 type: 'test' ...

Subtest: validateInstanceConfig throws when PLATFORM_FEE_PERCENT is negative

ok 5 - validateInstanceConfig throws when PLATFORM_FEE_PERCENT is negative

duration_ms: 0.27827 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.217925 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.206565 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.296126 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: 0.517882 type: 'test' ...

Subtest: validateInstanceConfig throws when optional URL is invalid

ok 10 - validateInstanceConfig throws when optional URL is invalid

duration_ms: 0.725462 type: 'test' ...

Subtest: validateInstanceConfig throws when optional URL has invalid protocol

ok 11 - validateInstanceConfig throws when optional URL has invalid protocol

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

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 25.069345

→ 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: 93.770956 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: 39.467353 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: 20.150353 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: 23.138068 type: 'test' ... 1..4

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 190.217805

→ 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: 363.418412 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.838467 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: 437.052143 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: 199.492442 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 124.121835 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: 133.837689 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: 169.447999 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: 161.489691 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: 382.271988 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: 74.038032 type: 'test' ... 1..10

tests 10

suites 0

pass 10

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2091.410127

→ 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: 378.207091 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: 25.425856 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: 334.97564 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: 120.257326 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 117.389591 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: 130.346206 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: 92.668757 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: 112.512903 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: 541.249822 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: 112.101397 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: 152.005521 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: 103.168875 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: 86.181372 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: 90.011313 type: 'test' ...

Subtest: Controller renders synchronous loadThread snapshots immediately

ok 15 - Controller renders synchronous loadThread snapshots immediately

duration_ms: 69.387004 type: 'test' ...

Subtest: VideoModalCommentController attaches profiles using normalized pubkeys

ok 16 - VideoModalCommentController attaches profiles using normalized pubkeys

duration_ms: 1.956861 type: 'test' ...

Subtest: VideoModalCommentController accepts mixed-case videoEventIds for snapshots

ok 17 - VideoModalCommentController accepts mixed-case videoEventIds for snapshots

duration_ms: 0.584945 type: 'test' ...

Subtest: VideoModal comment composer updates messaging and dispatches events

ok 18 - VideoModal comment composer updates messaging and dispatches events

duration_ms: 68.79726 type: 'test' ...

Subtest: VideoModalCommentController load preserves current video for submissions

ok 19 - VideoModalCommentController load preserves current video for submissions

duration_ms: 1.052946 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: 0.538592 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: 0.664124 type: 'test' ...

Subtest: VideoModalCommentController uses pointerIdentifiers root id fallback

ok 22 - VideoModalCommentController uses pointerIdentifiers root id fallback

duration_ms: 0.722736 type: 'test' ...

Subtest: VideoModalCommentController publishes comment using event id fallback

ok 23 - VideoModalCommentController publishes comment using event id fallback

duration_ms: 0.541161 type: 'test' ...

Subtest: VideoModalCommentController disposes safely during in-flight comment loads

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

duration_ms: 0.627148 type: 'test' ...

Subtest: VideoModalCommentController prompts login when publish requires authentication

ok 25 - VideoModalCommentController prompts login when publish requires authentication

duration_ms: 0.781306 type: 'test' ...

Subtest: VideoModalCommentController includes parent metadata when replying

ok 26 - VideoModalCommentController includes parent metadata when replying

duration_ms: 0.456002 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: 84.196493 type: 'test' ... 1..27

tests 27

suites 0

pass 27

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2713.406633

→ 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: 1.62576
  type: 'test'
  ...
# Subtest: ensureVideoModalReady loads modal if needs rehydrate
ok 2 - ensureVideoModalReady loads modal if needs rehydrate
  ---
  duration_ms: 0.439365
  type: 'test'
  ...
# Subtest: showModalWithPoster uses provided video
ok 3 - showModalWithPoster uses provided video
  ---
  duration_ms: 0.810293
  type: 'test'
  ...
# Subtest: showModalWithPoster falls back to current video
ok 4 - showModalWithPoster falls back to current video
  ---
  duration_ms: 0.507756
  type: 'test'
  ...
# Subtest: forceRemoveModalPoster calls modal method
ok 5 - forceRemoveModalPoster calls modal method
  ---
  duration_ms: 0.42951
  type: 'test'
  ...
1..5

ok 1 - VideoModalController

duration_ms: 5.660943 type: 'test' ... 1..1

tests 6

suites 0

pass 6

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 18.380404

→ 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: 399.028194 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: 28.515316 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: 420.693743 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: 165.950607 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 121.852759 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: 131.593488 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: 115.057392 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: 132.103135 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: 416.587612 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: 76.289849 type: 'test' ...

Subtest: CommentsController lifecycle manages comment references

ok 11 - CommentsController lifecycle manages comment references

duration_ms: 87.622987 type: 'test' ...

Subtest: ReactionsController delegates reaction updates

ok 12 - ReactionsController delegates reaction updates

duration_ms: 73.873351 type: 'test' ...

Subtest: SimilarContentController toggles section visibility

ok 13 - SimilarContentController toggles section visibility

duration_ms: 105.88161 type: 'test' ...

Subtest: ModerationController reset clears moderation overlay references

ok 14 - ModerationController reset clears moderation overlay references

duration_ms: 109.451924 type: 'test' ...

Subtest: VideoModal trims tag strip to fit modal width

ok 15 - VideoModal trims tag strip to fit modal width

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

tests 15

suites 0

pass 15

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 2572.069432

→ 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: 276.591118 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: 22.734805 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: 326.929627 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: 112.46127 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 91.64099 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: 87.446292 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: 90.455391 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: 108.211913 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: 371.950325 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: 69.321524 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: 13.20299
  type: 'test'
  ...
# Subtest: trusted mute blur state matches moderation context
ok 2 - trusted mute blur state matches moderation context
  ---
  duration_ms: 4.005037
  type: 'test'
  ...
1..2

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

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

tests 13

suites 0

pass 13

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1655.139434

→ 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: 356.373835 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: 24.845378 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: 330.544741 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: 120.52984 type: 'test' ...

Subtest: video modal sticky navigation responds to scroll direction

ok 5 - video modal sticky navigation responds to scroll direction

duration_ms: 92.716764 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: 93.27046 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: 87.980314 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: 90.748544 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: 374.016452 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: 72.229296 type: 'test' ...

Subtest: VideoModal renders tag metadata and toggles visibility

ok 11 - VideoModal renders tag metadata and toggles visibility

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

tests 11

suites 0

pass 11

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 1732.565328

→ 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: 3.213646 type: 'test' ...

Subtest: normalizes mode and boolean flags

ok 2 - normalizes mode and boolean flags

duration_ms: 0.454529 type: 'test' ...

Subtest: augments magnets with ws/xs hints

ok 3 - augments magnets with ws/xs hints

duration_ms: 2.162828 type: 'test' ...

Subtest: normalizes nip71 metadata collections

ok 4 - normalizes nip71 metadata collections

duration_ms: 1.12544 type: 'test' ...

Subtest: derives legacy duration fallback from imeta variants

ok 5 - derives legacy duration fallback from imeta variants

duration_ms: 0.432646 type: 'test' ...

Subtest: reports validation errors for missing fields

ok 6 - reports validation errors for missing fields

duration_ms: 0.307631 type: 'test' ...

Subtest: rejects insecure hosted URLs

ok 7 - rejects insecure hosted URLs

duration_ms: 0.372908 type: 'test' ...

Subtest: allows publishing with only imeta variants

ok 8 - allows publishing with only imeta variants

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

tests 8

suites 0

pass 8

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 23.671892

→ 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: 1.477003 type: 'test' ...

Subtest: convertEventToVideo normalizes nsfw and kids booleans

ok 2 - convertEventToVideo normalizes nsfw and kids booleans

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

tests 2

suites 0

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 14.28915

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

Subtest: VideoSettingsMenuController - requestMenu opens popover

ok 1 - VideoSettingsMenuController - requestMenu opens popover

duration_ms: 1.653638 type: 'test' ...

Subtest: VideoSettingsMenuController - closeMenu closes popover

ok 2 - VideoSettingsMenuController - closeMenu closes popover

duration_ms: 0.338327 type: 'test' ...

Subtest: VideoSettingsMenuController - requestMenu toggles if open

ok 3 - VideoSettingsMenuController - requestMenu toggles if open

duration_ms: 0.321362 type: 'test' ...

Subtest: VideoSettingsMenuController - closeAll closes all popovers

ok 4 - VideoSettingsMenuController - closeAll closes all popovers

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

tests 4

suites 0

pass 4

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 15.729003

→ 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: 22.977251
  type: 'test'
  ...
ok 2 - handleTorrentStream (firefox) should set up video correctly with highWaterMark
duration_ms: 21.630792
type: 'test'
...
# Subtest: handleTorrentStream (chrome) should handle CORS warning logic
ok 3 - handleTorrentStream (chrome) should handle CORS warning logic
---
duration_ms: 1.535075
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: 48.39021 type: 'suite' ... 1..1

tests 3

suites 1

pass 3

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 62.854462

→ 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: 6.751095
  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.616625
  type: 'test'
  ...
1..2

ok 1 - WebTorrent Regression Tests

duration_ms: 60.927675 type: 'suite' ... 1..1

tests 2

suites 1

pass 2

fail 0

cancelled 0

skipped 0

todo 0

duration_ms 68.513372

✔ All unit tests passed