11/** @vitest -environment jsdom */
22import { act , type ButtonHTMLAttributes , type InputHTMLAttributes , type ReactNode } from 'react'
3+ import { emcnMock } from '@sim/testing/mocks/emcn.mock'
4+ import { nextNavigationMock } from '@sim/testing/mocks/next-navigation.mock'
35import { createRoot , type Root } from 'react-dom/client'
46import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
57
@@ -8,11 +10,10 @@ const mocks = vi.hoisted(() => ({
810 chatVerify : vi . fn ( ) ,
911 fileRequest : vi . fn ( ) ,
1012 fileVerify : vi . fn ( ) ,
11- refresh : vi . fn ( ) ,
1213} ) )
1314
1415vi . mock ( '@sim/emcn' , ( ) => ( {
15- cn : ( ...values : Array < string | false | null | undefined > ) => values . filter ( Boolean ) . join ( ' ' ) ,
16+ ...emcnMock ,
1617 ChipInput : ( {
1718 error : _error ,
1819 size : _size ,
@@ -75,7 +76,7 @@ vi.mock('@/components/auth/public-auth-header', () => ({
7576vi . mock ( '@/app/f/[token]/public-file-auth-shell' , ( ) => ( {
7677 PublicFileAuthShell : ( { children } : { children : ReactNode } ) => < div > { children } </ div > ,
7778} ) )
78- vi . mock ( 'next/navigation' , ( ) => ( { useRouter : ( ) => ( { refresh : mocks . refresh } ) } ) )
79+ vi . mock ( 'next/navigation' , ( ) => nextNavigationMock )
7980vi . mock ( '@/hooks/queries/chats' , ( ) => ( {
8081 useChatEmailOtpRequest : ( ) => ( { mutateAsync : mocks . chatRequest , isPending : false } ) ,
8182 useChatEmailOtpVerify : ( ) => ( { mutateAsync : mocks . chatVerify , isPending : false } ) ,
@@ -96,6 +97,12 @@ function changeInput(input: HTMLInputElement, value: string) {
9697 input . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) )
9798}
9899
100+ function input ( selector : string ) {
101+ const found = container . querySelector < HTMLInputElement > ( selector )
102+ if ( ! found ) throw new Error ( `Missing input: ${ selector } ` )
103+ return found
104+ }
105+
99106function button ( label : string ) {
100107 const found = Array . from ( container . querySelectorAll ( 'button' ) ) . find (
101108 ( candidate ) => candidate . textContent ?. trim ( ) === label
@@ -114,7 +121,6 @@ function expectOtpInvalid(invalid: boolean) {
114121}
115122
116123beforeEach ( ( ) => {
117- vi . clearAllMocks ( )
118124 vi . stubGlobal ( 'IS_REACT_ACT_ENVIRONMENT' , true )
119125 mocks . chatRequest . mockResolvedValue ( { } )
120126 mocks . chatVerify . mockResolvedValue ( { } )
@@ -128,15 +134,12 @@ beforeEach(() => {
128134afterEach ( ( ) => {
129135 act ( ( ) => root . unmount ( ) )
130136 container . remove ( )
131- vi . unstubAllGlobals ( )
132137} )
133138
134139describe ( 'OTP error provenance' , ( ) => {
135140 it ( 'keeps the chat code valid on resend failure and marks only a failed verification invalid' , async ( ) => {
136141 act ( ( ) => root . render ( < EmailAuth identifier = 'chat-1' /> ) )
137- act ( ( ) =>
138- changeInput ( container . querySelector < HTMLInputElement > ( '#email' ) ! , 'member@example.com' )
139- )
142+ act ( ( ) => changeInput ( input ( '#email' ) , 'member@example.com' ) )
140143 await act ( async ( ) => button ( 'Continue' ) . click ( ) )
141144
142145 mocks . chatRequest . mockRejectedValueOnce ( new Error ( 'Delivery failed' ) )
@@ -145,18 +148,14 @@ describe('OTP error provenance', () => {
145148 expectOtpInvalid ( false )
146149
147150 mocks . chatVerify . mockRejectedValueOnce ( new Error ( 'Incorrect code' ) )
148- await act ( async ( ) =>
149- changeInput ( container . querySelector < HTMLInputElement > ( '[data-testid="otp-code"]' ) ! , '123456' )
150- )
151+ await act ( async ( ) => changeInput ( input ( '[data-testid="otp-code"]' ) , '123456' ) )
151152 expect ( container . textContent ) . toContain ( 'Incorrect code' )
152153 expectOtpInvalid ( true )
153154 } )
154155
155156 it ( 'keeps the public-file code valid on resend failure and marks only a failed verification invalid' , async ( ) => {
156157 act ( ( ) => root . render ( < PublicFileEmailAuth token = 'share-1' /> ) )
157- act ( ( ) =>
158- changeInput ( container . querySelector < HTMLInputElement > ( '#email' ) ! , 'member@example.com' )
159- )
158+ act ( ( ) => changeInput ( input ( '#email' ) , 'member@example.com' ) )
160159 await act ( async ( ) => button ( 'Continue' ) . click ( ) )
161160
162161 mocks . fileRequest . mockRejectedValueOnce ( new Error ( 'Delivery failed' ) )
@@ -165,9 +164,7 @@ describe('OTP error provenance', () => {
165164 expectOtpInvalid ( false )
166165
167166 mocks . fileVerify . mockRejectedValueOnce ( new Error ( 'Incorrect code' ) )
168- await act ( async ( ) =>
169- changeInput ( container . querySelector < HTMLInputElement > ( '[data-testid="otp-code"]' ) ! , '123456' )
170- )
167+ await act ( async ( ) => changeInput ( input ( '[data-testid="otp-code"]' ) , '123456' ) )
171168 expect ( container . textContent ) . toContain ( 'Incorrect code' )
172169 expectOtpInvalid ( true )
173170 } )
0 commit comments